<!DOCTYPE html><html lang="zh-CN" data-theme="light"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><title>Redis6基础知识 | 无人深空</title><meta name="keywords" content="Redis,DataBase"><meta name="author" content="zcm"><meta name="copyright" content="zcm"><meta name="format-detection" content="telephone=no"><meta name="theme-color" content="#ffffff"><meta name="description" content="Redis6 入门到精通1.NoSQL数据库简介 技术发展  技术的分类  解决功能性的问题：Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN 解决扩展性的问题：Struts、Spring、SpringMVC、Hibernate、Mybatis 解决性能的问题：NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch   Web1.0">
<meta property="og:type" content="article">
<meta property="og:title" content="Redis6基础知识">
<meta property="og:url" content="https://gitee.com/zcmmmm/zcmmmm/2022/10/12/Redis6%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/index.html">
<meta property="og:site_name" content="无人深空">
<meta property="og:description" content="Redis6 入门到精通1.NoSQL数据库简介 技术发展  技术的分类  解决功能性的问题：Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN 解决扩展性的问题：Struts、Spring、SpringMVC、Hibernate、Mybatis 解决性能的问题：NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch   Web1.0">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/Redis.png">
<meta property="article:published_time" content="2022-10-12T01:27:08.000Z">
<meta property="article:modified_time" content="2023-05-02T08:31:27.846Z">
<meta property="article:author" content="zcm">
<meta property="article:tag" content="Redis">
<meta property="article:tag" content="DataBase">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/Redis.png"><link rel="shortcut icon" href="/img/favicon.png"><link rel="canonical" href="https://gitee.com/zcmmmm/zcmmmm/2022/10/12/Redis6%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/"><link rel="preconnect" href="//cdn.jsdelivr.net"/><link rel="preconnect" href="//busuanzi.ibruce.info"/><link rel="stylesheet" href="/css/index.css"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free/css/all.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.css" media="print" onload="this.media='all'"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.min.css" media="print" onload="this.media='all'"><script>const GLOBAL_CONFIG = { 
  root: '/',
  algolia: undefined,
  localSearch: {"path":"/search.xml","preload":false,"languages":{"hits_empty":"找不到您查询的内容：${query}"}},
  translate: undefined,
  noticeOutdate: undefined,
  highlight: {"plugin":"highlighjs","highlightCopy":true,"highlightLang":true,"highlightHeightLimit":200},
  copy: {
    success: '复制成功',
    error: '复制错误',
    noSupport: '浏览器不支持'
  },
  relativeDate: {
    homepage: false,
    post: false
  },
  runtime: '天',
  date_suffix: {
    just: '刚刚',
    min: '分钟前',
    hour: '小时前',
    day: '天前',
    month: '个月前'
  },
  copyright: undefined,
  lightbox: 'fancybox',
  Snackbar: {"chs_to_cht":"你已切换为繁体","cht_to_chs":"你已切换为简体","day_to_night":"你已切换为深色模式","night_to_day":"你已切换为浅色模式","bgLight":"#49b1f5","bgDark":"#1f1f1f","position":"bottom-left"},
  source: {
    justifiedGallery: {
      js: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.js',
      css: 'https://cdn.jsdelivr.net/npm/flickr-justified-gallery/dist/fjGallery.min.css'
    }
  },
  isPhotoFigcaption: false,
  islazyload: false,
  isAnchor: false
}</script><script id="config-diff">var GLOBAL_CONFIG_SITE = {
  title: 'Redis6基础知识',
  isPost: true,
  isHome: false,
  isHighlightShrink: false,
  isToc: true,
  postUpdate: '2023-05-02 16:31:27'
}</script><noscript><style type="text/css">
  #nav {
    opacity: 1
  }
  .justified-gallery img {
    opacity: 1
  }

  #recent-posts time,
  #post-meta time {
    display: inline !important
  }
</style></noscript><script>(win=>{
    win.saveToLocal = {
      set: function setWithExpiry(key, value, ttl) {
        if (ttl === 0) return
        const now = new Date()
        const expiryDay = ttl * 86400000
        const item = {
          value: value,
          expiry: now.getTime() + expiryDay,
        }
        localStorage.setItem(key, JSON.stringify(item))
      },

      get: function getWithExpiry(key) {
        const itemStr = localStorage.getItem(key)

        if (!itemStr) {
          return undefined
        }
        const item = JSON.parse(itemStr)
        const now = new Date()

        if (now.getTime() > item.expiry) {
          localStorage.removeItem(key)
          return undefined
        }
        return item.value
      }
    }
  
    win.getScript = url => new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.src = url
      script.async = true
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    })
  
      win.activateDarkMode = function () {
        document.documentElement.setAttribute('data-theme', 'dark')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d')
        }
      }
      win.activateLightMode = function () {
        document.documentElement.setAttribute('data-theme', 'light')
        if (document.querySelector('meta[name="theme-color"]') !== null) {
          document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff')
        }
      }
      const t = saveToLocal.get('theme')
    
          if (t === 'dark') activateDarkMode()
          else if (t === 'light') activateLightMode()
        
      const asideStatus = saveToLocal.get('aside-status')
      if (asideStatus !== undefined) {
        if (asideStatus === 'hide') {
          document.documentElement.classList.add('hide-aside')
        } else {
          document.documentElement.classList.remove('hide-aside')
        }
      }
    
    const detectApple = () => {
      if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){
        document.documentElement.classList.add('apple')
      }
    }
    detectApple()
    })(window)</script><link rel="stylesheet" href="/css/categories.css?v1"><link rel="stylesheet" href="/css/navigation.css?v1"><link rel="stylesheet" href="/css/custom.css?v1"><meta name="generator" content="Hexo 6.3.0"></head><body><div id="sidebar"><div id="menu-mask"></div><div id="sidebar-menus"><div class="avatar-img is-center"><img src="https://s2.loli.net/2022/09/22/36LN4kcjYOd9HT8.jpg" onerror="onerror=null;src='/img/friend_404.gif'" alt="avatar"/></div><div class="sidebar-site-data site-data is-center"><a href="/archives/"><div class="headline">文章</div><div class="length-num">43</div></a><a href="/tags/"><div class="headline">标签</div><div class="length-num">25</div></a><a href="/categories/"><div class="headline">分类</div><div class="length-num">11</div></a></div><hr/><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div></div></div><div class="post" id="body-wrap"><header class="not-top-img" id="page-header"><nav id="nav"><span id="blog_name"><a id="site-name" href="/">无人深空</a></span><div id="menus"></div><div class="menus_items"><div class="menus_item"><a class="site-page" href="/"><i class="fa-fw fas fa-home"></i><span> 首页</span></a></div><div class="menus_item"><a class="site-page" href="/archives/"><i class="fa-fw fas fa-archive"></i><span> 时间轴</span></a></div><div class="menus_item"><a class="site-page" href="/tags/"><i class="fa-fw fas fa-tags"></i><span> 标签</span></a></div><div class="menus_item"><a class="site-page" href="/categories/"><i class="fa-fw fas fa-folder-open"></i><span> 分类</span></a></div></div><div id="nav-right"><div id="search-button"><a class="site-page social-icon search"><i class="fas fa-search fa-fw"></i></a></div><div id="toggle-menu"><a class="site-page"><i class="fas fa-bars fa-fw"></i></a></div></div></nav></header><main class="layout" id="content-inner"><div id="post"><div id="post-info"><h1 class="post-title">Redis6基础知识</h1><div id="post-meta"><div class="meta-firstline"><span class="post-meta-date"><i class="far fa-calendar-alt fa-fw post-meta-icon"></i><span class="post-meta-label">发表于</span><time class="post-meta-date-created" datetime="2022-10-12T01:27:08.000Z" title="发表于 2022-10-12 09:27:08">2022-10-12</time><span class="post-meta-separator">|</span><i class="fas fa-history fa-fw post-meta-icon"></i><span class="post-meta-label">更新于</span><time class="post-meta-date-updated" datetime="2023-05-02T08:31:27.846Z" title="更新于 2023-05-02 16:31:27">2023-05-02</time></span><span class="post-meta-categories"><span class="post-meta-separator">|</span><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/%E9%9D%A2%E7%BB%8F/">面经</a><i class="fas fa-angle-right post-meta-separator"></i><i class="fas fa-inbox fa-fw post-meta-icon"></i><a class="post-meta-categories" href="/categories/DataBase/">DataBase</a></span></div><div class="meta-secondline"><span class="post-meta-separator">|</span><span class="post-meta-wordcount"><i class="far fa-file-word fa-fw post-meta-icon"></i><span class="post-meta-label">字数总计:</span><span class="word-count">19.2k</span><span class="post-meta-separator">|</span><i class="far fa-clock fa-fw post-meta-icon"></i><span class="post-meta-label">阅读时长:</span><span>67分钟</span></span><span class="post-meta-separator">|</span><span class="post-meta-pv-cv" id="" data-flag-title="Redis6基础知识"><i class="far fa-eye fa-fw post-meta-icon"></i><span class="post-meta-label">阅读量:</span><span id="busuanzi_value_page_pv"><i class="fa-solid fa-spinner fa-spin"></i></span></span></div></div></div><article class="post-content" id="article-container"><h2 id="Redis6-入门到精通"><a href="#Redis6-入门到精通" class="headerlink" title="Redis6 入门到精通"></a><a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1Rv41177Af/">Redis6 入门到精通</a></h2><h3 id="1-NoSQL数据库简介"><a href="#1-NoSQL数据库简介" class="headerlink" title="1.NoSQL数据库简介"></a>1.NoSQL数据库简介</h3><ol>
<li><p>技术发展</p>
<ul>
<li><p>技术的分类</p>
<ul>
<li>解决功能性的问题：<code>Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN</code></li>
<li>解决扩展性的问题：<code>Struts、Spring、SpringMVC、Hibernate、Mybatis</code></li>
<li>解决性能的问题：<code>NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch</code></li>
</ul>
</li>
<li><p>Web1.0 时代</p>
<blockquote>
<p>数据访问量很有限，用一夫当关的高性能的单点服务器可以解决大部分问题</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/web1.0.png" alt="web1.0"></p>
</li>
<li><p>Web2.0 时代</p>
<blockquote>
<p>随着 Web2.0 的时代的到来，用户访问量大幅度提升，同时产生了大量的用户数据</p>
<p>加上后来的智能移动设备的普及，所有的互联网平台都面临了巨大的性能挑战</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/web2.0.png" alt="web2.0"></p>
</li>
<li><p>解决 CPU 及内存压力 </p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E8%A7%A3%E5%86%B3CPU%E5%8F%8A%E5%86%85%E5%AD%98%E5%8E%8B%E5%8A%9B.png" alt="解决CPU及内存压力"></p>
</li>
<li><p>解决 IO 压力</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E8%A7%A3%E5%86%B3IO%E5%8E%8B%E5%8A%9B.png" alt="解决IO压力"></p>
</li>
</ul>
</li>
<li><p>NoSQL 数据库</p>
<ul>
<li><p>NoSQL 概述</p>
<blockquote>
<p>NoSQL(NoSQL &#x3D; <strong>Not Only SQL</strong> )，意即不仅仅是 SQL，泛指<strong>非关系型的数据库</strong> </p>
<p>NoSQL 不依赖业务逻辑方式存储，而以简单的 <code>key-value</code> 模式存储</p>
<p>因此大大的增加了数据库的扩展能力</p>
<ul>
<li>不遵循 SQL 标准</li>
<li>不支持 ACID</li>
<li>远超于 SQL 的性能</li>
</ul>
</blockquote>
</li>
<li><p>适用场景</p>
<ul>
<li>对数据高并发的读写</li>
<li>海量数据的读写</li>
<li>对数据高可扩展性的</li>
<li><strong>用不着 sql 的和用了 sql 也不行的情况，请考虑用 NoSql</strong></li>
</ul>
</li>
<li><p>不适用场景</p>
<ul>
<li><p>需要事务支持</p>
</li>
<li><p>基于 sql 的结构化查询存储，处理复杂的关系，需要<code>即席查询</code></p>
<blockquote>
<p>即席查询（Ad Hoc）是用户根据自己的需求，灵活的选择查询条件，系统能够根据用户的选择生成相应的统计报表</p>
<p>即席查询与普通应用查询最大的不同是普通的应用查询是定制开发的，而即席查询是由用户自定义查询条件的</p>
</blockquote>
</li>
</ul>
</li>
<li><p>常见 NoSQL 数据库</p>
<ul>
<li><p><strong>Memcache</strong></p>
<blockquote>
<p>很早出现的NoSql数据库</p>
<p>数据都在内存中，一般不持久化</p>
<p>支持简单的 key-value 模式，支持类型单一</p>
<p>一般是作为缓存数据库辅助持久化的数据库</p>
</blockquote>
</li>
<li><p><strong>Redis</strong></p>
<blockquote>
<p>几乎覆盖了 Memcached 的绝大部分功能</p>
<p>数据都在内存中，支持持久化，主要用作备份恢复</p>
<p>除了支持简单的 key-value 模式，还支持多种数据结构的存储，比如 list、set、hash、zset 等</p>
<p><strong>一般是作为缓存数据库辅助持久化的数据库</strong></p>
</blockquote>
</li>
<li><p><strong>MongoDB</strong></p>
<blockquote>
<p>高性能、开源、模式自由（schema free）的<strong>文档型数据库</strong></p>
<p>数据都在内存中， 如果内存不足，把不常用的数据保存到硬盘</p>
<p>虽然是 key-value 模式，但是对 value（尤其是<strong>json</strong>）提供了丰富的查询功能</p>
<p>支持二进制数据及大型对象</p>
<p>可以根据数据的特点<strong>替代 RDBMS（基于关系模型的数据库系统）</strong> ，成为独立的数据库</p>
<p>或者配合RDBMS，存储特定的数据</p>
</blockquote>
</li>
<li><p><strong>HBase</strong></p>
<blockquote>
<p>HBase 是 <strong>Hadoop</strong> 项目中的数据库</p>
<p>它用于需要对大量的数据进行随机、实时的读写操作的场景中</p>
<p>HBase 的目标就是处理数据量<strong>非常庞大</strong>的表，可以用<strong>普通的计算机</strong>处理超过 <strong>10 亿行</strong>数据，还可处理有<strong>数百万列</strong>元素的数据表</p>
</blockquote>
</li>
<li><p><strong>Cassandra</strong></p>
<blockquote>
<p>Apache Cassandra 是一款免费的开源 NoSQL 数据库，其设计目的在于管理由大量商用服务器构建起来的庞大集群上的<strong>海量数据集</strong>（数据量通常达到 PB 级别）</p>
<p>在众多显著特性当中，Cassandra 最为卓越的长处是对写入及读取操作进行规模调整，而且其不强调主集群的设计思路能够以相对直观的方式简化各集群的创建与扩展流程</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>行式存储数据库（大数据时代）</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E8%A1%8C%E5%BC%8F%E5%AD%98%E5%82%A8.png" alt="行式存储"></p>
</li>
<li><p>列式数据库</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E5%88%97%E5%BC%8F%E5%AD%98%E5%82%A8.png" alt="列式存储"></p>
</li>
<li><p>图关系型数据库</p>
<blockquote>
<p>如 Neo4j</p>
<p>适用场景：好友推荐、交通网络</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E5%9B%BE%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93.png" alt="图关系型数据库"></p>
</li>
<li><p>DB-Engines 数据库排名</p>
<ul>
<li><a target="_blank" rel="noopener" href="http://db-engines.com/en/ranking">官方网址</a></li>
</ul>
</li>
</ol>
<h3 id="2-Redis6-概述和安装"><a href="#2-Redis6-概述和安装" class="headerlink" title="2.Redis6 概述和安装"></a>2.Redis6 概述和安装</h3><ol>
<li><p>Redis 概述</p>
<blockquote>
<p>Redis 是一个开源的 key-value 存储系统</p>
<p>和 Memcached 类似，它支持存储的 value 类型相对更多，包括 string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash（哈希类型）</p>
<p>这些数据类型都支持 push&#x2F;pop、add&#x2F;remove 及取交集并集和差集及更丰富的操作，而且<strong>这些操作都是原子性的</strong></p>
<p>在此基础上，Redis 支持各种不同方式的排序</p>
<p>与 memcached 一样，为了保证效率，数据都是缓存在内存中</p>
<p>区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件</p>
<p>并且在此基础上实现了 master-slave（主从）同步</p>
</blockquote>
</li>
<li><p>应用场景</p>
<ul>
<li><p>配合关系型数据库做高速缓存</p>
<blockquote>
<p>高频次，热门访问的数据，降低数据库IO</p>
<p>分布式架构，做 session 共享</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E9%85%8D%E5%90%88%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%E5%81%9A%E9%AB%98%E9%80%9F%E7%BC%93%E5%AD%98.png" alt="配合关系型数据库做高速缓存"></p>
</li>
<li><p>多样的数据结构存储持久化数据</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E5%A4%9A%E6%A0%B7%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E6%8C%81%E4%B9%85%E5%8C%96%E6%95%B0%E6%8D%AE.png" alt="多样的数据结构持久化数据"></p>
</li>
</ul>
</li>
<li><p>Redis 安装（redis-6.2.1）</p>
<ul>
<li><p><a target="_blank" rel="noopener" href="http://redis.io/">官网</a></p>
</li>
<li><p>安装步骤（虚拟机 test）</p>
<ul>
<li><p>准备工作</p>
<blockquote>
<p>下载安装最新版的gcc编译器</p>
</blockquote>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">yum install centos-release-scl scl-utils-build</span><br><span class="line">yum install -y devtoolset-8-toolchain</span><br><span class="line">scl enable devtoolset-8 bash</span><br><span class="line">gcc --version</span><br></pre></td></tr></table></figure>
</li>
<li><p>下载 <code>redis-6.2.1.tar.gz</code> 放 <code>/opt/software</code> 目录</p>
</li>
<li><p>解压到 <code>/opt/module</code></p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">tar -zxvf redis-6.2.1.tar.gz -C /opt/module/</span><br></pre></td></tr></table></figure>
</li>
<li><p>进入 redis 根目录</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /opt/module/redis-6.2.1</span><br></pre></td></tr></table></figure>
</li>
<li><p>执行 <code>make</code> 命令（只是编译好，如为准备 C 语言编译环境会报错 <code>Jemalloc/jemalloc.h：没有那个文件</code>）</p>
</li>
<li><p>继续执行 <code>make install</code></p>
</li>
<li><p>默认安装目录：<code>/usr/local/bin</code></p>
</li>
</ul>
</li>
<li><p>一些命令</p>
<blockquote>
<p>redis-benchmark：性能测试工具，可以在自己本子运行，看看自己本子性能如何<br>redis-check-aof：修复有问题的AOF文件，rdb和aof后面讲<br>redis-check-dump：修复有问题的dump.rdb文件<br>redis-sentinel：Redis集群使用<br>redis-server：Redis服务器启动命令<br>redis-cli：客户端，操作入口</p>
</blockquote>
</li>
<li><p>前台启动（不推荐）</p>
<blockquote>
<p>命令行窗口不能关闭，否则服务器停止</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E5%89%8D%E5%8F%B0%E5%90%AF%E5%8A%A8.png" alt="前台启动"></p>
</li>
<li><p>后台启动（推荐）</p>
<ul>
<li><p>备份 redis.conf</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cp /opt/module/redis-6.2.1/redis.conf /opt/module/redis-6.2.1/redis.conf.bak</span><br></pre></td></tr></table></figure>
</li>
<li><p>修改 redis.conf</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">vim /opt/module/redis-6.2.1/redis.conf</span><br></pre></td></tr></table></figure>

<blockquote>
<p>vim 搜索 <code>:/daemonize</code></p>
<p>将 <code>daemonize no</code> 改为 <code>daemonize yes</code></p>
</blockquote>
</li>
<li><p>Redis 启动</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">redis-server</span><br></pre></td></tr></table></figure>
</li>
<li><p>用客户端访问</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">redis-cli</span><br></pre></td></tr></table></figure>
</li>
<li><p>关闭客户端</p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">redis-cli shutdown</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
</ul>
</li>
<li><p>Redis 其他知识</p>
<blockquote>
<p>默认16个数据库，类似数组下标从 0 开始，初始默认使用 0 号库<br>使用命令 <code>select &lt;dbid&gt;</code> 来切换数据库。如: select 8<br>统一密码管理，所有库同样密码<br>dbsize：查看当前数据库的 key 的数量<br>flushdb：清空当前库<br>flushall：通杀全部库</p>
</blockquote>
<ul>
<li><p>Redis 是<strong>单线程 + 多路 IO 复用技术</strong></p>
<blockquote>
<p>Redis 是单线程+多路 IO 复用技术</p>
<p>多路复用是指使用一个线程来检查多个文件描述符（Socket）的就绪状态，比如调用 select 和 poll 函数，传入多个文件描述符，如果有一个文件描述符就绪则返回，否则阻塞直到超时</p>
<p>得到就绪状态后进行真正的操作可以在同一个线程里执行，也可以启动线程执行（比如使用线程池）</p>
<p><strong>串行   vs   多线程+锁（memcached） vs   单线程+多路IO复用(Redis)</strong></p>
</blockquote>
</li>
<li><p>Redis 与 Memcache 三点不同</p>
<ul>
<li>Redis 支持多数据类型</li>
<li>Redis 支持持久化</li>
<li>Redis 单线程 + 多路 IO 复用</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="3-常用五大数据类型"><a href="#3-常用五大数据类型" class="headerlink" title="3.常用五大数据类型"></a>3.常用五大数据类型</h3><ol>
<li><p><a target="_blank" rel="noopener" href="http://www.redis.cn/commands.html">常见数据类型基本操作命令</a></p>
<blockquote>
<p>String、List、Set、Hash、Zset</p>
</blockquote>
</li>
<li><p>Redis 健（key）</p>
<ul>
<li><p>常用命令</p>
<ul>
<li><p><code>keys *</code>：查看当前库所有 key  (匹配：keys *1)</p>
</li>
<li><p><code>exists key</code>：判断某个 key 是否存在</p>
</li>
<li><p><code>type key</code>：查看你的 key 是什么类型</p>
</li>
<li><p><code>del key</code>：删除指定的 key 数据</p>
</li>
<li><p><code>unlink key</code>：根据 value 选择非阻塞删除</p>
<blockquote>
<p>仅将 keys 从 keyspace 元数据中删除，真正的删除会在后续异步操作</p>
</blockquote>
</li>
<li><p><code>expire key 10</code>：10秒钟：为给定的 key 设置过期时间</p>
</li>
<li><p><code>ttl key</code>：查看还有多少秒过期，-1 表示永不过期，-2 表示已过期</p>
</li>
</ul>
</li>
<li><p>其他命令</p>
<ul>
<li><code>select</code>：命令切换数据库</li>
<li><code>dbsize</code>：查看当前数据库的key的数量</li>
<li><code>flushdb</code>：清空当前库</li>
<li><code>flushall</code>：通杀全部库</li>
</ul>
</li>
</ul>
</li>
<li><p>Redis 字符串（String）</p>
<blockquote>
<p>String 是 Redis 最基本的类型，可以理解成与 Memcached 一模一样的类型，一个 key 对应一个 value</p>
<p><strong>String 类型是二进制安全的</strong>。意味着 Redis 的 string 可以包含任何数据</p>
<p>比如 jpg 图片或者序列化的对象</p>
<p><strong>String 类型是 Redis 最基本的数据类型，一个 Redis 中字符串 value 最多可以是 512M</strong></p>
</blockquote>
<ul>
<li><p>常用命令</p>
<ul>
<li><p><code>set &lt;key&gt; &lt;value&gt;</code>：添加键值对</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/set.png" alt="set"></p>
<blockquote>
<p>NX：当数据库中 key 不存在时，可以将 key-value 添加数据库</p>
<p>XX：当数据库中 key 存在时，可以将 key-value 添加数据库，与 NX 参数互斥</p>
<p>EX：key 的超时秒数</p>
<p>PX：key 的超时毫秒数，与 EX 互斥</p>
</blockquote>
</li>
<li><p><code>get &lt;key&gt;</code>：查询对应键值</p>
</li>
<li><p><code>append &lt;key&gt; &lt;value&gt;</code>：将给定的 <code>&lt;value&gt;</code>  追加到原值的末尾</p>
</li>
<li><p><code>strlen &lt;key&gt;</code>：获得值的长度</p>
</li>
<li><p><code>setnx &lt;key&gt; &lt;value&gt;</code>：只有在 key 不存在时    设置 key 的值</p>
</li>
<li><p><code>incr &lt;key&gt;</code>：将 key 中储存的数字值 + 1</p>
<blockquote>
<p>原子操作</p>
<p>只能对数字值操作，如果为空，新增值为 1</p>
</blockquote>
</li>
<li><p><code>decr &lt;key&gt;</code>：将 key 中储存的数字值 - 1</p>
<blockquote>
<p>原子操作</p>
<p>只能对数字值操作，如果为空，新增值为 -1</p>
</blockquote>
</li>
<li><p><code>incrby/decrby &lt;key&gt; &lt;步长&gt;</code>：将 key 中储存的数字值增减（自定义步长）</p>
<blockquote>
<p>原子操作</p>
</blockquote>
</li>
<li><p><code>mset &lt;key1&gt; &lt;value1&gt; &lt;key2&gt; &lt;value2&gt; ..... </code>：同时设置一个或多个 key-value对</p>
</li>
<li><p><code>mget &lt;key1&gt; &lt;key2&gt; &lt;key3&gt; .....</code>：同时获取一个或多个 value  </p>
</li>
<li><p><code>msetnx &lt;key1&gt; &lt;value1&gt; &lt;key2&gt; &lt;value2&gt; ..... </code>：同时设置一个或多个 key-value 对，当且仅当所有给定 key 都不存在</p>
<blockquote>
<p><strong>原子性，有一个失败则都失败</strong></p>
</blockquote>
</li>
<li><p><code>getrange &lt;key&gt; &lt;起始位置&gt; &lt;结束位置&gt;</code>：获得值的范围，类似 java 中的 substring，前包，后包</p>
</li>
<li><p><code>setrange &lt;key&gt; &lt;起始位置&gt; &lt;value&gt;</code>：用 <code>&lt;value&gt;</code>  覆写 <code>&lt;key&gt;</code> 所储存的字符串值，从&lt;起始位置&gt;开始(索引从0开始)</p>
</li>
<li><p><code>setex &lt;key&gt; &lt;过期时间&gt; &lt;value&gt;</code>：设置键值的同时，设置过期时间，单位秒</p>
</li>
<li><p><code>getset &lt;key&gt; &lt;value&gt;</code>：以新换旧，设置了新值同时获得旧值</p>
</li>
</ul>
</li>
<li><p>数据结构</p>
<blockquote>
<p>String 的数据结构为简单动态字符串 (Simple Dynamic String——SDS)</p>
<p>是可以修改的字符串，内部结构实现上类似于 Java 的 ArrayList，采用预分配冗余空间的方式来减少内存的频繁分配</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/String%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.png" alt="String数据结构"></p>
<blockquote>
<p>如图中所示，内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len</p>
<p><strong>当字符串长度小于 1M 时，扩容都是加倍现有的空间</strong></p>
<p><strong>如果超过 1M，扩容时一次只会多扩 1M 的空间</strong></p>
<p><strong>需要注意的是字符串最大长度为 512M</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>Redis 列表（List）</p>
<blockquote>
<p><strong>单键多值</strong></p>
<p>Redis 列表是简单的字符串列表，按照插入顺序排序</p>
<p>你可以添加一个元素到列表的头部（左边）或者尾部（右边）</p>
<p>它的底层实际是个双向链表，对两端的操作性能很高，通过索引下标的操作中间的节点性能会较差</p>
</blockquote>
<ul>
<li><p>常用命令</p>
<ul>
<li><code>lpush/rpush &lt;key&gt; &lt;value1&gt; &lt;value2&gt; &lt;value3&gt; ....</code>：从左边&#x2F;右边插入一个或多个值</li>
<li><code>lpop/rpop &lt;key&gt;</code>：从左边&#x2F;右边吐出一个值。<strong>值在键在，值光键亡</strong></li>
<li><code>rpoplpush &lt;key1&gt; &lt;key2&gt;</code>：从 <code>&lt;key1&gt;</code> 列表右边吐出一个值，插到 <code>&lt;key2&gt;</code> 列表左边</li>
<li><code>lrange &lt;key&gt; &lt;start&gt; &lt;stop&gt;</code>：按照索引下标获得元素（从左到右）</li>
<li><code>lrange mylist 0 -1</code>：左边第一个，-1右边第一个<strong>（0-1表示获取所有）</strong></li>
<li><code>lindex &lt;key&gt; &lt;index&gt;</code>：按照索引下标获得元素（从左到右）</li>
<li><code>llen &lt;key&gt;</code>：获得列表长度 </li>
<li><code>linsert &lt;key&gt; before &lt;value&gt;&lt; newvalue&gt;</code>：在 value 的后面插入 newvalue 值</li>
<li><code>lrem &lt;key&gt; &lt;n&gt; &lt;value&gt;</code>：从左边删除 n 个 value(从左到右)</li>
<li><code>lset &lt;key&gt; &lt;index&gt; &lt;value&gt;</code>：将列表 key 下标为 index 的值替换成 value</li>
</ul>
</li>
<li><p>数据结构</p>
<blockquote>
<p>List 的数据结构为快速链表 <code>quickList</code></p>
<p>首先在列表元素较少的情况下会使用一块连续的内存存储，这个结构是 <code>ziplist</code>，即压缩列表，它将所有的元素紧挨着一起存储，分配的是一块连续的内存</p>
<p>当数据量比较多的时候才会改成 <code>quicklist</code></p>
<p><strong>因为普通的链表需要的附加指针空间太大，会比较浪费空间</strong></p>
<p>比如这个列表里存的只是 int 类型的数据，结构上还需要两个额外的指针 prev 和 next</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/List%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.png" alt="List数据结构"></p>
<blockquote>
<p><strong>Redis 将链表和 ziplist 结合起来组成了 quicklist</strong></p>
<p><strong>也就是将多个 ziplist 使用双向指针串起来使用</strong></p>
<p><strong>这样既满足了快速的插入删除性能，又不会出现太大的空间冗余</strong></p>
</blockquote>
</li>
</ul>
</li>
<li><p>Redis 集合（Set）</p>
<blockquote>
<p>Redis set 对外提供的功能与 list 类似是一个列表的功能，特殊之处在于 set 是可以<strong>自动排重</strong></p>
<p>当你需要存储一个列表数据，又不希望出现重复数据时，set 是一个很好的选择，并且set提供了判断某个成员是否在一个 set 集合内的重要接口，这个也是 list 所不能提供的</p>
<p>Redis 的 Set 是 string 类型的无序集合</p>
<p>它底层其实是一个 value 为 null 的 hash 表，所以添加、删除、查找的<strong>复杂度都是O(1)</strong></p>
<p>如果一个操作的时间复杂度为O(1)，则当数据增加时，该操作的时间不变</p>
</blockquote>
<ul>
<li><p>常用命令</p>
<ul>
<li><code>sadd &lt;key&gt; &lt;value1&gt; &lt;value2&gt; .....</code>：将一个或多个 member 元素加入到集合 key 中，已经存在的 member 元素将被忽略</li>
<li><code>smembers &lt;key&gt;</code>：取出该集合的所有值</li>
<li><code>sismember &lt;key&gt; &lt;value&gt;</code>：判断集合 key 是否为含有该 value 值，有1，没有0</li>
<li><code>scard &lt;key&gt;</code>：返回该集合的元素个数</li>
<li><code>srem &lt;key&gt; &lt;value1&gt; &lt;value2&gt; ....</code>：删除集合中的某些元素</li>
<li><code>spop &lt;key&gt;</code>：<strong>随机从该集合中吐出一个值</strong></li>
<li><code>srandmember &lt;key&gt; &lt;n&gt;</code>：随机从该集合中取出n个值。不会从集合中删除 </li>
<li><code>smove &lt;source&gt; &lt;destination&gt; value</code>：把集合中一个值从一个集合移动到另一个集合</li>
<li><code>sinter &lt;key1&gt; &lt;key2&gt;</code>：返回两个集合的交集元素</li>
<li><code>sunion &lt;key1&gt; &lt;key2&gt;</code>：返回两个集合的并集元素</li>
<li><code>sdiff &lt;key1&gt; &lt;key2&gt;</code>：返回两个集合的<strong>差集</strong>元素（key1 中的，不包含 key2 中的）</li>
</ul>
</li>
<li><p>数据结构</p>
<blockquote>
<p>Set 数据结构是 dict 字典，字典是用哈希表实现的</p>
<p>Java 中 HashSet 的内部实现使用的是 HashMap，只不过所有的 value 都指向同一个对象</p>
<p>Redis 的 set 结构也是一样，它的内部也使用 hash 结构，所有的 value 都指向同一个内部值</p>
</blockquote>
</li>
</ul>
</li>
<li><p>Redis 哈希（Hash）</p>
<blockquote>
<p>Redis hash 是一个键值对集合</p>
<p>Redis hash 是一个 string 类型的 field 和 value 的映射表，hash 特别适合用于存储对象</p>
<p>类似 Java 里面的 <code>Map&lt;String, Object&gt;</code></p>
</blockquote>
<blockquote>
<p>举例：用户 ID 为查找的 key，存储的 value 用户对象包含姓名，年龄，生日等信息，如果用普通的 key &#x2F; value 结构来存储</p>
<p>主要有以下 2 种存储方式：</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/hash%E4%B8%BE%E4%BE%8B.png" alt="hash举例"></p>
</blockquote>
<ul>
<li><p>常用命令</p>
<ul>
<li><code>hset &lt;key&gt; &lt;field&gt; &lt;value&gt;</code>：给 key 集合中的  field 键赋值 value</li>
<li><code>hget &lt;key1&gt; &lt;field&gt;</code>：从 key1 集合 field 取出 value </li>
<li><code>hmset &lt;key1&gt; &lt;field1&gt; &lt;value1&gt; &lt;field2&gt; &lt;value2&gt;...</code>：批量设置 hash 的值</li>
<li><code>hexists &lt;key1&gt; &lt;field&gt;</code>：查看哈希表 key 中，给定域 field 是否存在</li>
<li><code>hkeys &lt;key&gt;</code>：列出该 hash 集合的所有 field</li>
<li><code>hvals &lt;key&gt;</code>：列出该 hash 集合的所有 value</li>
<li><code>hincrby &lt;key&gt; &lt;field&gt; &lt;increment&gt;</code>：为哈希表 key 中的域 field 的值加上增量 1</li>
<li><code>hsetnx &lt;key&gt; &lt;field&gt; &lt;value&gt;</code>：将哈希表 key 中的域 field 的值设置为 value ，当且仅当域 field 不存在</li>
</ul>
</li>
<li><p>数据结构</p>
<blockquote>
<p>Hash 类型对应的数据结构是两种：ziplist（压缩列表），hashtable（哈希表）</p>
<p>当 field-value 长度较短且个数较少时，使用 ziplist，否则使用 hashtable</p>
</blockquote>
</li>
</ul>
</li>
<li><p>Redis 有序集合（Zset – sorted set）</p>
<blockquote>
<p>Redis 有序集合 zset 与普通集合 set 非常相似，是一个没有重复元素的字符串集合</p>
<p>不同之处是有序集合的每个成员都关联了一个<strong>评分（score）</strong>，<strong>以评分从低到高的方式排序集合中的成员</strong></p>
<p><strong>集合的成员是唯一的，但是评分可以是重复的</strong></p>
<p>因为元素是有序的，所以可以很快的根据评分（score）或者次序（position）来获取一个范围的元素</p>
<p>访问有序集合的中间元素也是非常快的，<strong>因此你能够使用有序集合作为一个没有重复成员的智能列表</strong></p>
</blockquote>
<ul>
<li><p>常用命令</p>
<ul>
<li><code>zadd &lt;key&gt; &lt;score1&gt; &lt;value1&gt; &lt;score2&gt; &lt;value2&gt;…</code>：将一个或多个 member 元素及其 score 值加入到有序集 key 当中</li>
<li><code>zrange &lt;key&gt; &lt;start&gt; &lt;stop&gt; [WITHSCORES]</code>：返回有序集 key 中，下标在 start  stop 之间的元素，带 WITHSCORES，可以让分数一起和值返回到结果集</li>
<li><code>zrangebyscore key min max [withscores] [limit offset count]</code>：返回有序集 key 中，所有 score 值介于 min 和 max 之间（包括等于 min 或 max ）的成员。有序集成员按 score 值递增次序排列</li>
<li><code>zrevrangebyscore key max min [withscores] [limit offset count]</code>：同上，改为从大到小排列</li>
<li><code>zincrby &lt;key&gt; &lt;increment&gt; &lt;value&gt;</code>：为元素的score加上增量</li>
<li><code>zrem &lt;key&gt; &lt;value&gt;</code>：删除该集合下，指定值的元素</li>
<li><code>zcount &lt;key&gt; &lt;min&gt; &lt;max&gt;</code>：统计该集合，分数区间内的元素个数 </li>
<li><code>zrank &lt;key&gt; &lt;value&gt;</code>：返回该值在集合中的排名，从0开始</li>
</ul>
</li>
<li><p>数据结构</p>
<blockquote>
<p>SortedSet(zset) 是 Redis 提供的一个非常特别的数据结构</p>
<p>一方面它等价于 Java 的数据结构 Map&lt;String, Double&gt;，可以给每一个元素 value 赋予一个权重 score</p>
<p>另一方面它又类似于 TreeSet，内部的元素会按照权重 score 进行排序，可以得到每个元素的名次，还可以通过 score 的范围来获取元素的列表</p>
<p>zset 底层使用了两个数据结构：</p>
<ul>
<li><p>hash：hash 的作用就是关联元素 value 和权重 score，保障元素 value 的唯一性，可以通过元素 value 找到相应的 score 值</p>
</li>
<li><p>跳跃表：跳跃表的目的在于给元素 value 排序，根据 score 的范围获取元素列表</p>
</li>
</ul>
</blockquote>
</li>
<li><p>跳跃表</p>
<blockquote>
<p>有序集合在生活中比较常见，例如根据成绩对学生排名，根据得分对玩家排名等</p>
<p>对于有序集合的底层实现，可以用数组、平衡树、链表等</p>
<ul>
<li><p>数组不便元素的插入、删除</p>
</li>
<li><p>平衡树或红黑树虽然效率高但结构复杂</p>
</li>
<li><p>链表查询需要遍历所有效率低</p>
</li>
<li><p><strong>Redis 采用的是跳跃表：跳跃表效率堪比红黑树，实现远比红黑树简单</strong></p>
</li>
</ul>
</blockquote>
<blockquote>
<p>实例：对比有序链表和跳跃表，从链表中查询出51</p>
<ul>
<li><p>有序链表</p>
<blockquote>
<p>要查找值为51的元素，需要从第一个元素开始依次查找、比较才能找到。共需要6次比较</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E6%9C%89%E5%BA%8F%E9%93%BE%E8%A1%A8.png" alt="有序链表"></p>
</li>
<li><p>跳跃表（效率更高）</p>
<blockquote>
<p>从第 2 层开始，1 节点比 51 节点小，向后比较</p>
<p>21 节点比 51 节点小，继续向后比较，后面就是 NULL 了，所以从 21 节点向下到第1层</p>
<p>在第1层，41 节点比 51 节点小，继续向后，61 节点比 51 节点大，所以从 41 向下</p>
<p>在第0层，51 节点为要查找的节点，节点被找到，共查找4次</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E8%B7%B3%E8%B7%83%E8%A1%A8.png" alt="跳跃表"></p>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
</ol>
<h3 id="4-Redis6-配置文件详解"><a href="#4-Redis6-配置文件详解" class="headerlink" title="4.Redis6 配置文件详解"></a>4.Redis6 配置文件详解</h3><ol>
<li><p>配置文件位置：<code>/opt/module/redis-6.2.1/redis.conf</code></p>
</li>
<li><p><strong>Units单位</strong></p>
<blockquote>
<p>配置大小单位,开头定义了一些基本的度量单位，只支持 bytes，不支持 bit</p>
<p>大小写不敏感</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013095509721.png" alt="image-20221013095509721"></p>
</li>
<li><p><strong>INCLUDES包含</strong></p>
<blockquote>
<p>类似 jsp 中的 include，多实例的情况可以把公用的配置文件提取出来</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013095544697.png" alt="image-20221013095544697"></p>
</li>
<li><p><strong>网络相关配置</strong></p>
<ul>
<li><p>bind</p>
<blockquote>
<p>注释掉 <code>bind 127.0.0.1 -::1</code></p>
<p>默认情况 bind&#x3D;127.0.0.1 只能接受本机的访问请求</p>
<p>不写的情况下，无限制接受任何 ip 地址的访问</p>
<p>生产环境肯定要你应用服务器的地址；服务器是需要远程访问的，所以需要将其注释掉</p>
<p>如果开启了 protected-mode，那么在没有设定 bind ip 且没有设密码的情况下，Redis 只允许接受本机的响应</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013095658350.png" alt="image-20221013095658350"></p>
</li>
<li><p><strong>protected-mode</strong></p>
<blockquote>
<p>将本机访问保护模式设置 no</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013095737853.png" alt="image-20221013095737853"></p>
</li>
<li><p><strong>Port</strong></p>
<blockquote>
<p>端口号，默认 6379</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013095956861.png" alt="image-20221013095956861"></p>
</li>
<li><p><strong>tcp-backlog</strong></p>
<blockquote>
<p>设置 tcp 的 backlog，backlog 其实是一个连接队列</p>
<p><strong>backlog 队列总和 &#x3D; 未完成三次握手队列 + 已经完成三次握手队列</strong></p>
<p>在高并发环境下你需要一个高 backlog 值来避免慢客户端连接问题</p>
<p>注意 Linux 内核会将这个值减小到 <code>/proc/sys/net/core/somaxconn</code> 的值（128），所以需要确认增大 <code>/proc/sys/net/core/somaxconn</code>和 <code>/proc/sys/net/ipv4/tcp_max_syn_backlog</code>（128）两个值来达到想要的效果</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100124988.png" alt="image-20221013100124988"></p>
</li>
<li><p><strong>timeout</strong></p>
<blockquote>
<p>一个空闲的客户端维持多少秒会关闭，0 表示关闭该功能（即永不关闭）</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100151588.png" alt="image-20221013100151588"></p>
</li>
<li><p><strong>tcp-keepalive</strong></p>
<blockquote>
<p>对访问客户端的一种心跳检测，每个 n 秒检测一次</p>
<p>单位为秒，如果设置为 0，则不会进行 Keepalive 检测，建议设置成 60 </p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100228869.png" alt="image-20221013100228869"></p>
</li>
</ul>
</li>
<li><p><strong>GENERAL 通用配置</strong></p>
<ul>
<li><p><strong>daemonize</strong></p>
<blockquote>
<p>是否为后台进程，设置为 yes</p>
<p>守护进程，后台启动</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100343725.png" alt="image-20221013100343725"></p>
</li>
<li><p><strong>pidfile</strong></p>
<blockquote>
<p>存放 pid 文件的位置，每个实例会产生一个不同的 pid 文件</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100411006.png" alt="image-20221013100411006"></p>
</li>
<li><p><strong>loglevel</strong></p>
<blockquote>
<p>指定日志记录级别，Redis 总共支持四个级别：</p>
<p>debug、verbose、notice、warning，默认为 <strong>notice</strong></p>
<p>四个级别根据使用阶段来选择，生产环境选择 notice 或者 warning</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100453420.png" alt="image-20221013100453420"></p>
</li>
<li><p><strong>logfile</strong></p>
<blockquote>
<p>日志文件系统</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100512954.png" alt="image-20221013100512954"></p>
</li>
<li><p>databases 16</p>
<blockquote>
<p>设定库的数量默认16，默认数据库为 0，可以使用 <code>SELECT &lt;dbid&gt;</code> 命令在连接上指定数据库 id</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100542702.png" alt="image-20221013100542702"></p>
</li>
</ul>
</li>
<li><p><strong>SECURITY 安全</strong></p>
<ul>
<li><p>设置密码</p>
<blockquote>
<p>访问密码的查看、设置和取消</p>
<p>在命令中设置密码，只是临时的</p>
<p>重启redis服务器，密码就还原了</p>
<p>永久设置，需要再配置文件中进行设置</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100708227.png" alt="image-20221013100708227"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013100654615.png" alt="image-20221013100654615"></p>
</li>
</ul>
</li>
<li><p><strong>LIMITS限制</strong></p>
<ul>
<li><p><strong>maxclients</strong></p>
<blockquote>
<p>设置 redis 同时可以与多少个客户端进行连接</p>
<p>默认情况下为 10000 个客户端</p>
<p>如果达到了此限制，redis 则会拒绝新的连接请求，并且向这些连接请求方发出 <code>max number of clients reached</code> 以作回应</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013101412121.png" alt="image-20221013101412121"></p>
</li>
<li><p><strong>maxmemory</strong> </p>
<blockquote>
<p>建议必须设置，否则可能将内存占满，造成服务器宕机</p>
<p>设置 redis 可以使用的内存量</p>
<p>一旦到达内存使用上限，redis 将会试图移除内部数据，移除规则可以通过 <code>maxmemory-policy</code> 来指定</p>
<p>如果 redis 无法根据移除规则来移除内存中的数据，或者设置了<code>不允许移除</code>，那么 redis 则会针对那些需要申请内存的指令返回错误信息，比如 SET、LPUSH 等</p>
<p>但是对于无内存申请的指令，仍然会正常响应，比如 GET 等</p>
<p>如果当前 redis 是主 redis（说明当前 redis 有从 redis），那么在设置内存使用上限时，需要在系统中留出一些内存空间给同步队列缓存，只有在设置的是<code>不移除</code>的情况下，才不用考虑这个因素</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013101609368.png" alt="image-20221013101609368"></p>
</li>
<li><p><strong>maxmemory-policy</strong></p>
<blockquote>
<p><code>volatile-lru</code>：使用 LRU 算法移除 key，只对设置了过期时间的键（最近最少使用）</p>
<p><code>allkeys-lru</code>：在所有集合 key 中，使用 LRU 算法移除 key</p>
<p><code>volatile-random</code>：在过期集合中移除随机的 key，只对设置了过期时间的键</p>
<p><code>allkeys-random</code>：在所有集合 key 中，移除随机的 key</p>
<p><code>volatile-ttl</code>：移除那些 TTL 值最小的 key，即那些最近要过期的 key</p>
<p><code>noeviction</code>：不进行移除。针对写操作，只是返回错误信息</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013101738342.png" alt="image-20221013101738342"></p>
</li>
<li><p><strong>maxmemory-samples</strong></p>
<blockquote>
<p>设置样本数量，LRU 算法和最小 TTL 算法都并非是精确的算法，而是估算值</p>
<p>所以可以设置样本的大小，redis 默认会检查这么多个 key 并选择其中 LRU 的那个</p>
<p>一般设置 3 到 7 的数字，数值越小样本越不准确，但性能消耗越小</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013101919497.png" alt="image-20221013101919497"></p>
</li>
</ul>
</li>
</ol>
<h3 id="5-Redis6-的发布和订阅"><a href="#5-Redis6-的发布和订阅" class="headerlink" title="5.Redis6 的发布和订阅"></a>5.Redis6 的发布和订阅</h3><ol>
<li><p>什么是发布和订阅</p>
<blockquote>
<p><strong>Redis 发布订阅（pub &#x2F; sub）是一种消息通信模式：</strong></p>
<p><strong>发送者 (pub) 发送消息，订阅者 (sub) 接收消息</strong></p>
<p><strong>Redis 客户端可以订阅任意数量的频道</strong></p>
</blockquote>
</li>
<li><p>Redis 的发布和订阅</p>
<ul>
<li><p>客户端订阅频道</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013103213138.png" alt="image-20221013103213138"></p>
</li>
<li><p>客户端 &#x2F; 服务器端向频道发送消息</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013103219773.png" alt="image-20221013103219773"></p>
</li>
</ul>
</li>
<li><p>发布与订阅的命令行实现</p>
<ul>
<li><p>打开客户端订阅 channel1</p>
<blockquote>
<p>subscribe channel1</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013103350788.png" alt="image-20221013103350788"></p>
</li>
<li><p>打开另一个客户端，给 channel1 发送消息 hello</p>
<blockquote>
<p>publish channel1 hello</p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013103359312.png" alt="image-20221013103359312"></p>
</li>
<li><p>订阅 channel1 的客户端收到消息 hello</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013103437924.png" alt="image-20221013103437924"></p>
</li>
<li><p>注意事项</p>
<blockquote>
<p>如果发布的消息没有持久化，则客户端只能收到订阅后发布的消息</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h3 id="6-Redis6-新数据类型"><a href="#6-Redis6-新数据类型" class="headerlink" title="6.Redis6 新数据类型"></a>6.Redis6 新数据类型</h3><ol>
<li><p><strong>Bitmaps</strong></p>
<ul>
<li><p>简介</p>
<blockquote>
<p>现代计算机用二进制（位） 作为信息的基础单位， 1 个字节等于 8 位，</p>
<p>例如 <code>abc</code> 字符串是由 3 个字节组成， 但实际在计算机存储时将其用二进制表示，</p>
<p><code>abc</code> 分别对应的 ASCII 码分别是 97、 98、 99， 对应的二进制分别是 01100001、 01100010 和 01100011，如下图</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013104301761.png" alt="image-20221013104301761"></p>
</blockquote>
<blockquote>
<p>合理地使用操作位能够有效地提高内存使用率和开发效率</p>
<p>Redis 提供 Bitmaps 这个<code>数据类型</code>可以实现对位的操作</p>
</blockquote>
<ul>
<li>Bitmaps 本身不是一种数据类型， 实际上它就是字符串（key-value） ， 但是它可以对字符串的位进行操作</li>
<li>Bitmaps 单独提供了一套命令， 所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同</li>
<li>可以把 Bitmaps 想象成一个以位为单位的数组， 数组的每个单元只能存储 0 和 1， 数组的下标在 Bitmaps 中叫做偏移量</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013104434809.png" alt="image-20221013104434809"></p>
</li>
<li><p>常用命令</p>
<ul>
<li><p><strong>setbit</strong></p>
<blockquote>
<p><code>setbit &lt;key&gt; &lt;offset&gt; &lt;value&gt;</code>：设置 Bitmaps 中某个偏移量的值（0或1）</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013104552396.png" alt="image-20221013104552396"></p>
<p>offset：偏移量从 0 开始</p>
</blockquote>
<blockquote>
<p>实例</p>
<p>每个独立用户是否访问过网站存放在 Bitmaps 中， 将访问的用户记做1， 没有访问的用户记做0， 用偏移量作为用户的 id</p>
<p>设置键的第 offset 个位的值（从0算起） ， 假设现在有 20 个用户，userid&#x3D;1，6， 11，15，19 的用户对网站进行了访问， 那么当前 Bitmaps 初始化结果如图</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013104717446.png" alt="image-20221013104717446"></p>
<p><code>unique:users:20201106</code> 代表 2020-11-06 这天的独立访问用户的 Bitmaps</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013104809833.png" alt="image-20221013104809833"></p>
<p>很多应用的用户 id 以一个指定数字（例如10000） 开头， 直接将用户 id 和 Bitmaps 的偏移量对应势必会造成一定的浪费， 通常的做法是每次做 setbit 操作时将用户 id 减去这个指定数字</p>
<p><strong>在第一次初始化 Bitmaps 时， 假如偏移量非常大， 那么整个初始化过程执行会比较慢， 可能会造成 Redis 的阻塞</strong></p>
</blockquote>
</li>
<li><p><strong>getbit</strong></p>
<blockquote>
<p><code>getbit &lt;key&gt; &lt;offset&gt;</code>：获取 Bitmaps 中某个偏移量的值</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013104928036.png" alt="image-20221013104928036"></p>
<p>获取键的第 offset 位的值（从0开始算）</p>
</blockquote>
<blockquote>
<p>实例</p>
<p>获取 id&#x3D;8 的用户是否在 2020-11-06 这天访问过， 返回 0 说明没有访问过</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013105005186.png" alt="image-20221013105005186"></p>
<p><strong>注：因为 100 根本不存在，所以也是返回 0</strong></p>
</blockquote>
</li>
<li><p><strong>bitcount</strong></p>
<blockquote>
<p>统计<strong>字符串</strong>被设置为 1 的 bit 数</p>
<p>一般情况下，给定的整个字符串都会被进行计数，通过指定额外的 start 或 end 参数，可以让计数只在特定的位上进行</p>
<p>start 和 end 参数的设置，都可以使用负数值：比如 -1 表示最后一个位，而 -2 表示倒数第二个位，start、end 是指 bit 组的字节的下标数，二者皆包含</p>
<p><code>bitcount &lt;key&gt; [start end]</code>：统计字符串<strong>从 start 字节到 end 字节</strong>比特值为 1 的数量</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013105159522.png" alt="image-20221013105159522"></p>
</blockquote>
<blockquote>
<p>实例1</p>
<p>计算 2022-11-06 这天的独立访问用户数量</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013105226353.png" alt="image-20221013105226353"></p>
<p><strong>start 和 end 代表起始和结束字节数</strong>，下面操作计算用户 id 在第 1 个字节到第 3 个字节之间的独立访问用户数， 对应的用户 id 是 11，15，19</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013105303465.png" alt="image-20221013105303465"></p>
</blockquote>
<blockquote>
<p>实例2</p>
<p>K1&#x3D;[01000001 01000000  00000000 00100001]，对应字节 [0 1 2 3]</p>
<ul>
<li><code>bitcount K1 1 2</code>： 统计下标 1、2 字节组中 bit&#x3D;1 的个数，即 01000000 00000000（输出 1）</li>
<li><code>bitcount K1 1 3</code>： 统计下标 1、3 字节组中 bit&#x3D;1 的个数，即01000000 00000000 00100001（输出 3）</li>
<li><code>bitcount K1 0 -2</code>： 统计下标 0 到下标倒数第 2，字节组中 bit&#x3D;1 的个数，即01000001 01000000  00000000（输出 3）</li>
</ul>
</blockquote>
<p> <strong>注意：redis 的 setbit 设置或清除的是 bit 位置，而 bitcount 计算的是 byte 位置</strong></p>
</li>
<li><p><strong>bitop</strong></p>
<blockquote>
<p><code>bitop and(or/not/xor) &lt;destkey&gt; [key…]</code></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013105756743.png" alt="image-20221013105756743"></p>
<p>bitop是一个复合操作</p>
<p>实现可以做多个 Bitmaps 的 and（交集）、or（并集）、not（非）、xor（异或） 操作并将结果保存在 destkey 中</p>
</blockquote>
<blockquote>
<p>实例</p>
<p><strong>2020-11-04 日访问网站的userid&#x3D;1,2,5,9</strong></p>
<p>setbit unique:users:20201104 1 1</p>
<p>setbit unique:users:20201104 2 1</p>
<p>setbit unique:users:20201104 5 1</p>
<p>setbit unique:users:20201104 9 1</p>
<p><strong>2020-11-03 日访问网站的userid&#x3D;0,1,4,9</strong></p>
<p>setbit unique:users:20201103 0 1</p>
<p>setbit unique:users:20201103 1 1</p>
<p>setbit unique:users:20201103 4 1</p>
<p>setbit unique:users:20201103 9 1</p>
<p><strong>计算出两天都访问过网站的用户数量</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013105918873.png" alt="image-20221013105918873"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013110025284.png" alt="image-20221013110025284"></p>
<p><strong>计算出任意一天都访问过网站的用户数量（例如月活跃就是类似这种），可以使用 or 求并集</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013110044622.png" alt="image-20221013110044622"></p>
</blockquote>
</li>
</ul>
</li>
<li><p>Bitmaps 与 set 对比</p>
<blockquote>
<p>假设网站有 1 亿用户， 每天独立访问的用户有 5 千万， 如果每天用集合类型和 Bitmaps 分别存储活跃用户可以得到表</p>
<table>
<thead>
<tr>
<th>数据类型</th>
<th align="center">每个用户id占用空间</th>
<th align="center">需要存储的用户量</th>
<th align="center">全部内存量</th>
</tr>
</thead>
<tbody><tr>
<td>集合类型</td>
<td align="center">64位</td>
<td align="center">50000000</td>
<td align="center">64位*50000000 &#x3D; 400MB</td>
</tr>
<tr>
<td>Bitmaps</td>
<td align="center">1位</td>
<td align="center">100000000</td>
<td align="center">1位*100000000 &#x3D; 12.5MB</td>
</tr>
</tbody></table>
<p>很明显， 这种情况下使用 Bitmaps 能节省很多的内存空间， 尤其是随着时间推移节省的内存还是非常可观的</p>
<table>
<thead>
<tr>
<th align="center">数据类型</th>
<th align="center">一天</th>
<th align="center">一个月</th>
<th align="center">一年</th>
</tr>
</thead>
<tbody><tr>
<td align="center">集合类型</td>
<td align="center">400MB</td>
<td align="center">12GB</td>
<td align="center">144GB</td>
</tr>
<tr>
<td align="center">Bitmaps</td>
<td align="center">12.5MB</td>
<td align="center">375MB</td>
<td align="center">4.5GB</td>
</tr>
</tbody></table>
</blockquote>
<blockquote>
<p>但 Bitmaps 并不是万金油， 假如该网站每天的独立访问用户很少， 例如只有 10 万（大量的僵尸用户） </p>
<p>那么两者的对比如下表所示， 很显然， 这时候使用 Bitmaps 就不太合适了， 因为基本上大部分位都是 0</p>
<table>
<thead>
<tr>
<th align="center">数据类型</th>
<th align="center">每个userid占用空间</th>
<th align="center">需要存储的用户量</th>
<th align="center">全部内存量</th>
</tr>
</thead>
<tbody><tr>
<td align="center">集合类型</td>
<td align="center">64位</td>
<td align="center">100000</td>
<td align="center">64位*100000 &#x3D; 800KB</td>
</tr>
<tr>
<td align="center">Bitmaps</td>
<td align="center">1位</td>
<td align="center">100000000</td>
<td align="center">1位*100000000 &#x3D; 12.5MB</td>
</tr>
</tbody></table>
</blockquote>
</li>
</ul>
</li>
<li><p><strong>HyperLogLog</strong></p>
<ul>
<li><p>简介</p>
<blockquote>
<p>在工作当中，我们经常会遇到与统计相关的功能需求，比如统计网站 PV（PageView 页面访问量）,可以使用 Redis 的 incr、incrby 轻松实现</p>
<p>但像 UV（UniqueVisitor，独立访客）、独立 IP 数、搜索记录数等需要去重和计数的问题如何解决？<strong>这种求集合中不重复元素个数的问题称为基数问题</strong></p>
<p>解决基数问题有很多种方案：</p>
<ul>
<li><p>数据存储在 MySQL 表中，使用 distinct count 计算不重复个数</p>
</li>
<li><p>使用 Redis 提供的 hash、set、bitmaps 等数据结构来处理</p>
</li>
</ul>
<p>以上的方案结果精确，但随着数据不断增加，导致占用空间越来越大，对于非常大的数据集是不切实际的</p>
<p><strong>能否能够降低一定的精度来平衡存储空间？Redis 推出了 HyperLogLog</strong></p>
<p><strong>Redis HyperLogLog 是用来做基数统计的算法</strong></p>
<p><strong>HyperLogLog 的优点是，在输入元素的数量或者体积非常非常大时，计算基数所需的空间总是固定的、并且是很小的</strong></p>
<p>在 Redis 里面，每个 HyperLogLog 键只需要花费 12 KB 内存，就可以计算接近 2^64 个不同元素的基数。这和计算基数时，元素越多耗费内存就越多的集合形成鲜明对比</p>
<p>但是，因为 HyperLogLog 只会根据输入元素来计算基数，而不会储存输入元素本身，所以 HyperLogLog 不能像集合那样返回输入的各个元素</p>
<p>什么是基数?</p>
<p>比如数据集 {1, 3, 5, 7, 5, 7, 8}， 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}，基数（不重复元素）为 5</p>
<p><strong>基数估计就是在误差可接受的范围内，快速计算基数</strong></p>
</blockquote>
</li>
<li><p>常用命令</p>
<ul>
<li><p><strong>pfadd</strong></p>
<blockquote>
<p><code>pfadd &lt;key&gt; &lt; element&gt; [element ...]</code> ：添加指定元素到 HyperLogLog 中</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013131601170.png" alt="image-20221013131601170"></p>
</blockquote>
<blockquote>
<p>实例</p>
<p>将所有元素添加到指定 HyperLogLog 数据结构中</p>
<p>如果执行命令后 HLL 估计的近似基数发生变化，则返回 1，否则返回 0</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013131649960.png" alt="image-20221013131649960"></p>
</blockquote>
</li>
<li><p><strong>pfcount</strong></p>
<blockquote>
<p><code>pfcount &lt;key&gt; [key ...]</code>：计算HLL的近似基数，可以计算多个 HLL，比如用HLL存储每天的 UV，计算一周的 UV 可以使用 7 天的 UV 合并计算即可</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013131914780.png" alt="image-20221013131914780"></p>
</blockquote>
<blockquote>
<p>实例</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013131925563.png" alt="image-20221013131925563"></p>
</blockquote>
</li>
<li><p><strong>pfmerge</strong></p>
<blockquote>
<p><code>pfmerge &lt;destkey&gt; &lt;sourcekey&gt; [sourcekey ...]</code>：将一个或多个 HLL 合并后的结果存储在另一个 HLL 中，比如每月活跃用户可以使用每天的活跃用户来合并计算可得</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013131741608.png" alt="image-20221013131741608"></p>
</blockquote>
<blockquote>
<p>实例</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013131757566.png" alt="image-20221013131757566"></p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Geospatial</strong></p>
<ul>
<li><p>简介</p>
<blockquote>
<p>Redis 3.2 中增加了对 GEO 类型的支持</p>
<p>GEO，即 Geographic，地理信息的缩写</p>
<p>该类型是元素的 2 维坐标，在地图上就是经纬度</p>
<p>redis 基于该类型，提供了经纬度设置，查询，范围查询，距离查询，经纬度 Hash 等常见操作</p>
</blockquote>
</li>
<li><p>常用命令</p>
<ul>
<li><p><strong>geoadd</strong></p>
<blockquote>
<p><code>geoadd &lt;key&gt; &lt; longitude&gt; &lt;latitude&gt; &lt;member&gt; [longitude latitude member...]</code>：添加地理位置（经度，纬度，名称）</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132144309.png" alt="image-20221013132144309"></p>
</blockquote>
<blockquote>
<p>实例</p>
<p><code>geoadd china:city 121.47 31.23 shanghai</code></p>
<p><code>geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing</code></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132212971.png" alt="image-20221013132212971"></p>
<p><strong>两极无法直接添加，一般会下载城市数据，直接通过 Java 程序一次性导入</strong></p>
<p><strong>有效的经度从 -180 度到 180 度</strong></p>
<p><strong>有效的纬度从 -85.05112878 度到 85.05112878 度</strong></p>
<p><strong>当坐标位置超出指定范围时，该命令将会返回一个错误</strong></p>
<p><strong>已经添加的数据，是无法再次往里面添加的</strong></p>
</blockquote>
</li>
<li><p><strong>geopos</strong></p>
<blockquote>
<p><code>geopos &lt;key&gt; &lt;member&gt; [member...]</code>：获得指定地区的坐标值</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132357676.png" alt="image-20221013132357676"></p>
</blockquote>
<blockquote>
<p>实例</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132410816.png" alt="image-20221013132410816"></p>
</blockquote>
</li>
<li><p><strong>geodist</strong></p>
<blockquote>
<p><code>geodist &lt;key&gt; &lt;member1&gt; &lt;member2&gt; [m|km|ft|mi ]</code>：获取两个位置之间的直线距离</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132444659.png" alt="image-20221013132444659"></p>
</blockquote>
<blockquote>
<p>实例</p>
<p>获取两个位置之间的直线距离</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132459547.png" alt="image-20221013132459547"></p>
<p>m 表示单位为米[默认值]</p>
<p>km 表示单位为千米</p>
<p>mi 表示单位为英里</p>
<p>ft 表示单位为英尺</p>
<p>如果用户没有显式地指定单位参数， 那么 GEODIST 默认使用米作为单位</p>
</blockquote>
</li>
<li><p><strong>georadius</strong></p>
<blockquote>
<p><code>georadius &lt;key&gt; &lt;longitude&gt; &lt;latitude&gt; radius m|km|ft|mi</code>：以给定的经纬度为中心，找出某一半径内的元素</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132543211.png" alt="image-20221013132543211"></p>
</blockquote>
<blockquote>
<p>实例</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013132601653.png" alt="image-20221013132601653"></p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="7-Jedis-操作-Redis6"><a href="#7-Jedis-操作-Redis6" class="headerlink" title="7.Jedis 操作 Redis6"></a>7.Jedis 操作 Redis6</h3><ol>
<li><p>Jedis 所需 jar 包</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">groupId</span>&gt;</span>redis.clients<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>jedis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">version</span>&gt;</span>3.2.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>链接 Redis 注意事项</p>
<ul>
<li><p>禁用 Linux 的防火墙：Linux(CentOS7) 里执行命令</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">systemctl stop/disable firewalld.service</span><br></pre></td></tr></table></figure>
</li>
<li><p>redis.conf 中注释掉 <code>bind 127.0.0.1</code>，然后 <code>protected-mode no</code></p>
</li>
</ul>
</li>
<li><p>Jedis 常用操作</p>
<ul>
<li><p>创建测试程序</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> com.atguigu.jedis;</span><br><span class="line"><span class="keyword">import</span> redis.clients.jedis.Jedis;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">Demo01</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">Jedis</span> <span class="variable">jedis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Jedis</span>(<span class="string">&quot;192.168.10.120&quot;</span>,<span class="number">6379</span>);</span><br><span class="line">        <span class="comment">// redis 配置文件中设置的密码</span></span><br><span class="line">        jedis.auth(<span class="string">&quot;123456&quot;</span>);</span><br><span class="line">        <span class="type">String</span> <span class="variable">pong</span> <span class="operator">=</span> jedis.ping();</span><br><span class="line">        System.out.println(<span class="string">&quot;连接成功：&quot;</span>+pong);</span><br><span class="line">        jedis.close();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>Jedis-API——key</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">jedis.set(<span class="string">&quot;k1&quot;</span>, <span class="string">&quot;v1&quot;</span>);</span><br><span class="line">jedis.set(<span class="string">&quot;k2&quot;</span>, <span class="string">&quot;v2&quot;</span>);</span><br><span class="line">jedis.set(<span class="string">&quot;k3&quot;</span>, <span class="string">&quot;v3&quot;</span>);</span><br><span class="line">Set&lt;String&gt; keys = jedis.keys(<span class="string">&quot;*&quot;</span>);</span><br><span class="line">System.out.println(keys.size());</span><br><span class="line">    <span class="keyword">for</span> (String key : keys) &#123;</span><br><span class="line">    System.out.println(key);</span><br><span class="line">&#125;</span><br><span class="line">System.out.println(jedis.exists(<span class="string">&quot;k1&quot;</span>));</span><br><span class="line">System.out.println(jedis.ttl(<span class="string">&quot;k1&quot;</span>));                </span><br><span class="line">System.out.println(jedis.get(<span class="string">&quot;k1&quot;</span>));</span><br></pre></td></tr></table></figure>
</li>
<li><p>Jedis-API——String</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">jedis.mset(<span class="string">&quot;str1&quot;</span>,<span class="string">&quot;v1&quot;</span>,<span class="string">&quot;str2&quot;</span>,<span class="string">&quot;v2&quot;</span>,<span class="string">&quot;str3&quot;</span>,<span class="string">&quot;v3&quot;</span>);</span><br><span class="line">System.out.println(jedis.mget(<span class="string">&quot;str1&quot;</span>,<span class="string">&quot;str2&quot;</span>,<span class="string">&quot;str3&quot;</span>));</span><br></pre></td></tr></table></figure>
</li>
<li><p>Jedis-API——List</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">List&lt;String&gt; list = jedis.lrange(<span class="string">&quot;mylist&quot;</span>,<span class="number">0</span>,-<span class="number">1</span>);</span><br><span class="line"><span class="keyword">for</span> (String element : list) &#123;</span><br><span class="line">	System.out.println(element);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>Jedis-API——Set</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">jedis.sadd(<span class="string">&quot;orders&quot;</span>, <span class="string">&quot;order01&quot;</span>);</span><br><span class="line">jedis.sadd(<span class="string">&quot;orders&quot;</span>, <span class="string">&quot;order02&quot;</span>);</span><br><span class="line">jedis.sadd(<span class="string">&quot;orders&quot;</span>, <span class="string">&quot;order03&quot;</span>);</span><br><span class="line">jedis.sadd(<span class="string">&quot;orders&quot;</span>, <span class="string">&quot;order04&quot;</span>);</span><br><span class="line">Set&lt;String&gt; smembers = jedis.smembers(<span class="string">&quot;orders&quot;</span>);</span><br><span class="line"><span class="keyword">for</span> (String order : smembers) &#123;</span><br><span class="line">	System.out.println(order);</span><br><span class="line">&#125;</span><br><span class="line">jedis.srem(<span class="string">&quot;orders&quot;</span>, <span class="string">&quot;order02&quot;</span>);</span><br></pre></td></tr></table></figure>
</li>
<li><p>Jedis-API——Hash</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">jedis.hset(<span class="string">&quot;hash1&quot;</span>,<span class="string">&quot;userName&quot;</span>,<span class="string">&quot;lisi&quot;</span>);</span><br><span class="line">System.out.println(jedis.hget(<span class="string">&quot;hash1&quot;</span>,<span class="string">&quot;userName&quot;</span>));</span><br><span class="line">Map&lt;String,String&gt; map = <span class="keyword">new</span> <span class="title class_">HashMap</span>&lt;String,String&gt;();</span><br><span class="line">map.put(<span class="string">&quot;telphone&quot;</span>,<span class="string">&quot;13810169999&quot;</span>);</span><br><span class="line">map.put(<span class="string">&quot;address&quot;</span>,<span class="string">&quot;atguigu&quot;</span>);</span><br><span class="line">map.put(<span class="string">&quot;email&quot;</span>,<span class="string">&quot;abc@163.com&quot;</span>);</span><br><span class="line">jedis.hmset(<span class="string">&quot;hash2&quot;</span>,map);</span><br><span class="line">List&lt;String&gt; result = jedis.hmget(<span class="string">&quot;hash2&quot;</span>, <span class="string">&quot;telphone&quot;</span>,<span class="string">&quot;email&quot;</span>);</span><br><span class="line"><span class="keyword">for</span> (String element : result) &#123;</span><br><span class="line">	System.out.println(element);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
</li>
<li><p>Jedis-API——Zset</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">jedis.zadd(<span class="string">&quot;zset01&quot;</span>, <span class="number">100d</span>, <span class="string">&quot;z3&quot;</span>);</span><br><span class="line">jedis.zadd(<span class="string">&quot;zset01&quot;</span>, <span class="number">90d</span>, <span class="string">&quot;l4&quot;</span>);</span><br><span class="line">jedis.zadd(<span class="string">&quot;zset01&quot;</span>, <span class="number">80d</span>, <span class="string">&quot;w5&quot;</span>);</span><br><span class="line">jedis.zadd(<span class="string">&quot;zset01&quot;</span>, <span class="number">70d</span>, <span class="string">&quot;z6&quot;</span>);</span><br><span class="line"> </span><br><span class="line">Set&lt;String&gt; zrange = jedis.zrange(<span class="string">&quot;zset01&quot;</span>, <span class="number">0</span>, -<span class="number">1</span>);</span><br><span class="line"><span class="keyword">for</span> (String e : zrange) &#123;</span><br><span class="line">	System.out.println(e);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ul>
</li>
<li><p><a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1Rv41177Af/?p=19">案例实操：完成手机验证码功能</a></p>
<ul>
<li>要求<ul>
<li>输入手机号，点击发送后随机生成 6 位数字码，2 分钟有效</li>
<li>输入验证码，点击验证，返回成功或失败</li>
<li>每个手机号每天只能输入 3 次</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="8-Redis6-与-Spring-Boot-整合"><a href="#8-Redis6-与-Spring-Boot-整合" class="headerlink" title="8.Redis6 与 Spring Boot 整合"></a>8.Redis6 与 Spring Boot 整合</h3><ol>
<li><p>在 <code>pom.xml</code> 文件中引入 redis 相关依赖</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="comment">&lt;!-- redis --&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">&lt;!-- spring2.X集成redis所需common-pool2--&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.commons<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>commons-pool2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">version</span>&gt;</span>2.6.0<span class="tag">&lt;/<span class="name">version</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p><code>application.properties</code> 配置 redis 相关配置</p>
<figure class="highlight properties"><table><tr><td class="code"><pre><span class="line"><span class="comment">#Redis服务器地址</span></span><br><span class="line"><span class="attr">spring.redis.host</span>=<span class="string">192.168.10.120</span></span><br><span class="line"><span class="comment">#Redis服务器连接端口</span></span><br><span class="line"><span class="attr">spring.redis.port</span>=<span class="string">6379</span></span><br><span class="line"><span class="comment">#如果有密码</span></span><br><span class="line"><span class="attr">redis.password</span>=<span class="string">123456</span></span><br><span class="line"><span class="comment">#Redis数据库索引（默认为0）</span></span><br><span class="line"><span class="attr">spring.redis.database</span>= <span class="string">0</span></span><br><span class="line"><span class="comment">#连接超时时间（毫秒）</span></span><br><span class="line"><span class="attr">spring.redis.timeout</span>=<span class="string">1800000</span></span><br><span class="line"><span class="comment">#连接池最大连接数（使用负值表示没有限制）</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.max-active</span>=<span class="string">20</span></span><br><span class="line"><span class="comment">#最大阻塞等待时间(负数表示没限制)</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.max-wait</span>=<span class="string">-1</span></span><br><span class="line"><span class="comment">#连接池中的最大空闲连接</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.max-idle</span>=<span class="string">5</span></span><br><span class="line"><span class="comment">#连接池中的最小空闲连接</span></span><br><span class="line"><span class="attr">spring.redis.lettuce.pool.min-idle</span>=<span class="string">0</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>添加 redis 配置类</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisConfig</span> <span class="keyword">extends</span> <span class="title class_">CachingConfigurerSupport</span> &#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> RedisTemplate&lt;String, Object&gt; <span class="title function_">redisTemplate</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">        RedisTemplate&lt;String, Object&gt; template = <span class="keyword">new</span> <span class="title class_">RedisTemplate</span>&lt;&gt;();</span><br><span class="line">        RedisSerializer&lt;String&gt; redisSerializer = <span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>();</span><br><span class="line">        <span class="type">Jackson2JsonRedisSerializer</span> <span class="variable">jackson2JsonRedisSerializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Jackson2JsonRedisSerializer</span>(Object.class);</span><br><span class="line">        <span class="type">ObjectMapper</span> <span class="variable">om</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);</span><br><span class="line">        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);</span><br><span class="line">        jackson2JsonRedisSerializer.setObjectMapper(om);</span><br><span class="line">        template.setConnectionFactory(factory);</span><br><span class="line"><span class="comment">//key序列化方式</span></span><br><span class="line">        template.setKeySerializer(redisSerializer);</span><br><span class="line"><span class="comment">//value序列化</span></span><br><span class="line">        template.setValueSerializer(jackson2JsonRedisSerializer);</span><br><span class="line"><span class="comment">//value hashmap序列化</span></span><br><span class="line">        template.setHashValueSerializer(jackson2JsonRedisSerializer);</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">public</span> CacheManager <span class="title function_">cacheManager</span><span class="params">(RedisConnectionFactory factory)</span> &#123;</span><br><span class="line">        RedisSerializer&lt;String&gt; redisSerializer = <span class="keyword">new</span> <span class="title class_">StringRedisSerializer</span>();</span><br><span class="line">        <span class="type">Jackson2JsonRedisSerializer</span> <span class="variable">jackson2JsonRedisSerializer</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Jackson2JsonRedisSerializer</span>(Object.class);</span><br><span class="line"><span class="comment">//解决查询缓存转换异常的问题</span></span><br><span class="line">        <span class="type">ObjectMapper</span> <span class="variable">om</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectMapper</span>();</span><br><span class="line">        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);</span><br><span class="line">        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);</span><br><span class="line">        jackson2JsonRedisSerializer.setObjectMapper(om);</span><br><span class="line"><span class="comment">// 配置序列化（解决乱码的问题）,过期时间600秒</span></span><br><span class="line">        <span class="type">RedisCacheConfiguration</span> <span class="variable">config</span> <span class="operator">=</span> RedisCacheConfiguration.defaultCacheConfig()</span><br><span class="line">                .entryTtl(Duration.ofSeconds(<span class="number">600</span>))</span><br><span class="line">                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))</span><br><span class="line">                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))</span><br><span class="line">                .disableCachingNullValues();</span><br><span class="line">        <span class="type">RedisCacheManager</span> <span class="variable">cacheManager</span> <span class="operator">=</span> RedisCacheManager.builder(factory)</span><br><span class="line">                .cacheDefaults(config)</span><br><span class="line">                .build();</span><br><span class="line">        <span class="keyword">return</span> cacheManager;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>测试</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping(&quot;/redisTest&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">RedisTestController</span> &#123;</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate redisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@GetMapping</span></span><br><span class="line">    <span class="keyword">public</span> String <span class="title function_">testRedis</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="comment">//设置值到redis</span></span><br><span class="line">        redisTemplate.opsForValue().set(<span class="string">&quot;name&quot;</span>,<span class="string">&quot;lucy&quot;</span>);</span><br><span class="line">        <span class="comment">//从redis获取值</span></span><br><span class="line">        <span class="type">String</span> <span class="variable">name</span> <span class="operator">=</span> (String)redisTemplate.opsForValue().get(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">        <span class="keyword">return</span> name;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ol>
<h3 id="9-Redis6-的事务操作"><a href="#9-Redis6-的事务操作" class="headerlink" title="9.Redis6 的事务操作"></a>9.Redis6 的事务操作</h3><ol>
<li><p>Redis 的事务定义</p>
<blockquote>
<p><strong>Redis 事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行</strong></p>
<p>事务在执行的过程中，不会被其他客户端发送来的命令请求所打断</p>
<p><strong>Redis 事务的主要作用就是串联多个命令防止别的命令插队</strong></p>
</blockquote>
</li>
<li><p><strong>Multi、Exec、discard</strong></p>
<blockquote>
<p>从输入 Multi 命令开始，输入的命令都会依次进入命令队列中，但不会执行，直到输入 Exec后，Redis 会将之前的命令队列中的命令依次执行</p>
<p>组队的过程中可以通过 discard 来放弃组队</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013135314482.png" alt="image-20221013135314482"></p>
</blockquote>
<blockquote>
<p>案例1：组队成功，提交成功</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013135344421.png" alt="image-20221013135344421"></p>
<p>案例2：组队阶段报错，提交失败</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013135437111.png" alt="image-20221013135437111"></p>
<p>案例3：组队成功，提交阶段有成功有失败</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013135420516.png" alt="image-20221013135420516"></p>
</blockquote>
</li>
<li><p>Redis 事务的错误处理</p>
<ul>
<li><p>组队过程中某个命令出现了报告错误，执行时整个的所有队列都会被取消</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013135537287.png" alt="image-20221013135537287"></p>
</li>
<li><p>如果执行阶段某个命令报出了错误，则只有报错的命令不会被执行，而其他的命令都会执行，不会回滚</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013135547886.png" alt="image-20221013135547886"></p>
</li>
</ul>
</li>
<li><p>事务冲突问题</p>
<ul>
<li><p>需求</p>
<blockquote>
<p>一个请求想给金额减 8000</p>
<p>一个请求想给金额减 5000</p>
<p>一个请求想给金额减 1000</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013140024041.png" alt="image-20221013140024041"></p>
</li>
<li><p>悲观锁</p>
<blockquote>
<p>悲观锁（Pessimistic Lock)），每次去拿数据的时候都认为别人会修改，所以每次在拿数据的时候都会上锁，这样别人想拿这个数据就会 block 直到它拿到锁</p>
<p>传统的关系型数据库里边就用到了很多这种锁机制，比如行锁，表锁等，读锁，写锁等，都是在做操作之前先上锁</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013140119634.png" alt="image-20221013140119634"></p>
</li>
<li><p>乐观锁</p>
<blockquote>
<p>乐观锁（Optimistic Lock），每次去拿数据的时候都认为别人不会修改，所以不会上锁，但是在更新的时候会判断一下在此期间别人有没有去更新这个数据，可以使用版本号等机制</p>
<p>乐观锁适用于多读的应用类型，这样可以提高吞吐量</p>
<p><strong>Redis 就是利用这种 check-and-set 机制实现事务的</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013140219083.png" alt="image-20221013140219083"></p>
</li>
<li><p><strong><code>WATCH key [key...]</code></strong></p>
<blockquote>
<p>在执行 multi 之前，先执行 <code>watch key1 [key2]</code>，可以监视一个（或多个）key </p>
<p><strong>如果在事务执行之前这个（或这些）key 被其他命令所改动，那么事务将被打断</strong></p>
</blockquote>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013140702659.png" alt="image-20221013140702659"></p>
</li>
<li><p><strong>unwatch</strong></p>
<blockquote>
<p>取消 WATCH 命令对所有 key 的监视</p>
<p>如果在执行 WATCH 命令之后，EXEC 命令或 DISCARD 命令先被执行了的话，那么就不需要再执行 UNWATCH 了</p>
</blockquote>
</li>
</ul>
</li>
<li><p>Redis 事务三大特性</p>
<ul>
<li><p>单独的隔离操作</p>
<blockquote>
<p>事务中的所有命令都会序列化、按顺序地执行</p>
<p>事务在执行的过程中，不会被其他客户端发送来的命令请求所打断</p>
</blockquote>
</li>
<li><p>没有隔离级别的概念</p>
<blockquote>
<p>队列中的命令没有提交之前都不会实际被执行，因为事务提交前任何指令都不会被实际执行</p>
</blockquote>
</li>
<li><p>不保证原子性</p>
<blockquote>
<p>事务中如果有一条命令执行失败，其后的命令仍然会被执行，没有回滚</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h3 id="秒杀案例"><a href="#秒杀案例" class="headerlink" title="秒杀案例"></a><a target="_blank" rel="noopener" href="https://www.bilibili.com/video/BV1Rv41177Af?p=24">秒杀案例</a></h3><h3 id="10-Redis6-持久化之-RDB（Redis-Database-Backup-file）"><a href="#10-Redis6-持久化之-RDB（Redis-Database-Backup-file）" class="headerlink" title="10.Redis6 持久化之 RDB（Redis Database Backup file）"></a>10.Redis6 持久化之 RDB（Redis Database Backup file）</h3><ol>
<li><p>RDB 是什么</p>
<blockquote>
<p>是 redis 的一种持久化方式</p>
<p>在指定的时间间隔内将内存中的数据集快照写入磁盘， 即 Snapshot 快照，它恢复时是将快照文件直接读到内存里</p>
</blockquote>
</li>
<li><p>备份如何执行</p>
<blockquote>
<p>Redis 会单独创建（fork）一个子进程来进行持久化，会先将数据写入到 一个临时文件中，待持久化过程都结束了，再用这个临时文件替换上次持久化好的文件</p>
<p>整个过程中，主进程是不进行任何 IO 操作的，这就确保了极高的性能</p>
<p><strong>如果需要进行大规模数据的恢复，且对于数据恢复的完整性不是非常敏感，那 RDB 方式要比 AOF 方式更加的高效</strong></p>
<p><strong>RDB的缺点是最后一次持久化后的数据可能丢失</strong></p>
</blockquote>
</li>
<li><p>Fork</p>
<ul>
<li><p>Fork 的作用是复制一个与当前进程一样的进程</p>
<blockquote>
<p><strong>新进程的所有数据（变量、环境变量、程序计数器等） 数值都和原进程一致，但是是一个全新的进程，并作为原进程的子进程</strong></p>
</blockquote>
</li>
<li><p>在 Linux 程序中，<code>fork()</code> 会产生一个和父进程完全相同的子进程，</p>
<blockquote>
<p>但子进程在此后多会 exec 系统调用，出于效率考虑，Linux 中引入了<code>写时复制技术</code></p>
</blockquote>
</li>
<li><p>一般情况父进程和子进程会共用同一段物理内存，只有进程空间的各段的内容要发生变化时，才会将父进程的内容复制一份给子进程</p>
</li>
</ul>
</li>
<li><p>RDB 持久化流程</p>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/RDB%E6%8C%81%E4%B9%85%E5%8C%96%E6%B5%81%E7%A8%8B.png" alt="RDB持久化流程"></p>
</li>
<li><p><code>dump.rdb</code> 文件</p>
<blockquote>
<p>在 redis.conf 中配置文件名称，默认为 <code>dump.rdb</code></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013143226343.png" alt="image-20221013143226343"></p>
<p>rdb 文件的保存路径，也可以修改（默认为 Redis 启动时命令行所在的目录下）</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013143257530.png" alt="image-20221013143257530"></p>
</blockquote>
</li>
<li><p>如何触发 RDB 快照：保持策略</p>
<ul>
<li><p><strong>配置文件中默认的快照配置</strong></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013143445227.png" alt="image-20221013143445227"></p>
</li>
<li><p><strong>命令 save VS bgsave</strong></p>
<blockquote>
<p>save ：save 时只管保存，其它不管，全部阻塞。手动保存（不建议使用）</p>
<p>bgsave：Redis 会在后台异步进行快照操作， <strong>同时还可以响应客户端请求</strong></p>
<p>可以通过 lastsave 命令获取最后一次成功执行快照的时间</p>
</blockquote>
</li>
<li><p><strong>flushall 命令</strong></p>
<blockquote>
<p>执行flushall命令，也会产生dump.rdb文件，但里面是空的，无意义</p>
</blockquote>
</li>
<li><p><strong>stop-writes-on-bgsave-error</strong></p>
<blockquote>
<p>当 Redis 无法写入磁盘的话，直接关掉 Redis 的写操作。推荐 yes</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013144719727.png" alt="image-20221013144719727"></p>
</li>
<li><p><strong>rdbcompression 压缩文件</strong></p>
<blockquote>
<p>对于存储到磁盘中的快照，可以设置是否进行压缩存储</p>
<p>如果是的话，redis 会采用 LZF 算法进行压缩</p>
<p>如果你不想消耗 CPU 来进行压缩的话，可以设置为关闭此功能。推荐yes</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013144813829.png" alt="image-20221013144813829"></p>
</li>
<li><p><strong>rdbchecksum 检查完整性</strong></p>
<blockquote>
<p>在存储快照后，还可以让 redis 使用 CRC64 算法来进行数据校验</p>
<p>但是这样做会增加大约 10% 的性能消耗，如果希望获取到最大的性能提升，可以关闭此功能（推荐 yes)</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013144853226.png" alt="image-20221013144853226"></p>
</li>
<li><p>rdb 的备份</p>
<blockquote>
<p>先通过 config get dir 查询 rdb 文件的目录 </p>
<p>将 *.rdb 的文件拷贝到别的地方</p>
<p>rdb 的恢复：</p>
<ul>
<li>关闭 Redis</li>
<li>先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb</li>
<li>启动 Redis，备份数据会直接加载</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li><p>RDB 总结</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/RDB%E6%80%BB%E7%BB%93.png" alt="RDB总结"></p>
<ul>
<li><p>RDB 优势</p>
<ul>
<li><p>适合大规模的数据恢复</p>
</li>
<li><p>对数据完整性和一致性要求不高更适合使用</p>
</li>
<li><p>节省磁盘空间</p>
</li>
<li><p>恢复速度快</p>
</li>
</ul>
</li>
<li><p>RDB 劣势</p>
<ul>
<li>Fork 的时候，内存中的数据被克隆了一份，大致 2 倍的膨胀性需要考虑</li>
<li>虽然 Redis 在 fork 时使用了<strong>写时拷贝技术</strong>，但是如果数据庞大时还是比较消耗性能</li>
<li><strong>在备份周期在一定间隔时间做一次备份，所以如果 Redis 意外 down 掉的话，就会丢失最后一次快照后的所有修改</strong></li>
</ul>
</li>
<li><p>如何停止</p>
<blockquote>
<p>动态停止RDB：<code>redis-cli config set save &quot;&quot;</code></p>
<p>save后给空值，表示禁用保存策略</p>
</blockquote>
</li>
</ul>
</li>
</ol>
<h3 id="11-Redis6-持久化之-AOF（Append-Only-File）"><a href="#11-Redis6-持久化之-AOF（Append-Only-File）" class="headerlink" title="11.Redis6 持久化之 AOF（Append Only File）"></a>11.Redis6 持久化之 AOF（Append Only File）</h3><ol>
<li><p>AOF 是什么</p>
<blockquote>
<p>以<strong>日志</strong>的形式来记录每个写操作（增量保存），将 Redis 执行过的所有写指令记录下来(<strong>读操作不记录</strong>)， <strong>只许追加文件但不可以改写文件</strong></p>
<p>redis 启动之初会读取该文件重新构建数据，即 redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作</p>
</blockquote>
</li>
<li><p>AOF 持久化流程</p>
<ul>
<li><p>客户端的请求写命令会被 append 追加到 AOF 缓冲区内</p>
</li>
<li><p>AOF 缓冲区根据 AOF 持久化策略 <code>[always,everysec,no]</code> 将操作 sync 同步到磁盘的 AOF 文件中</p>
</li>
<li><p>AOF 文件大小超过重写策略或手动重写时，会对 AOF 文件 rewrite 重写，压缩 AOF 文件容量</p>
</li>
<li><p>Redis 服务重启时，会重新 load 加载 AOF 文件中的写操作达到数据恢复的目的</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/AOF%E6%B5%81%E7%A8%8B.png" alt="AOF流程"></p>
</li>
</ul>
</li>
<li><p><strong>AOF 默认不开启</strong></p>
<blockquote>
<p>可以在 <code>redis.conf</code> 中配置文件名称，默认为 <code>appendonly.aof</code></p>
<p>AOF 文件的保存路径，同 RDB 的路径一致</p>
</blockquote>
</li>
<li><p><strong>AOF 启动 &#x2F; 修复 &#x2F; 恢复</strong></p>
<blockquote>
<p>AOF 的备份机制和性能虽然和 RDB 不同, 但是备份和恢复的操作同 RDB 一样，都是拷贝备份文件，需要恢复时再拷贝到 Redis 工作目录下，启动系统即加载</p>
</blockquote>
<ul>
<li><p>正常恢复</p>
<blockquote>
<p>修改默认的 <code>appendonly no</code>，改为 <code>yes</code></p>
<p>将有数据的 <code>aof</code> 文件复制一份保存到对应目录（查看目录：<code>config get dir</code>）</p>
<p>恢复：重启 redis 然后重新加载</p>
</blockquote>
</li>
<li><p>异常恢复</p>
<blockquote>
<p>修改默认的 <code>appendonly no</code>，改为 <code>yes</code></p>
<p>如遇到 AOF 文件损坏，通过 <code>/usr/local/bin/redis-check-aof--fix appendonly.aof</code> 进行恢复</p>
<p>备份被写坏的 AOF 文件</p>
<p>恢复：重启 redis 然后重新加载</p>
</blockquote>
</li>
</ul>
</li>
<li><p><strong>AOF 同步频率设置</strong></p>
<blockquote>
<p><code>appendfsync always</code>：始终同步，每次 Redis 的写入都会立刻记入日志；性能较差但数据完整性比较好</p>
<p><code>appendfsync everysec</code>：每秒同步，每秒记入日志一次，如果宕机，本秒的数据可能丢失</p>
<p><code>appendfsync no</code>：redis不主动进行同步，把同步时机交给操作系统</p>
</blockquote>
</li>
<li><p><strong>Rewrite 压缩</strong></p>
<ul>
<li><p>什么是 rewrite</p>
<blockquote>
<p>AOF 采用文件追加方式，文件会越来越大</p>
<p>为避免出现此种情况，新增了重写机制</p>
<p>当 AOF 文件的大小超过所设定的阈值时，Redis 就会启动 AOF 文件的内容压缩， 只保留可以恢复数据的最小指令集</p>
<p>可以使用命令 <code>bgrewriteaof</code></p>
</blockquote>
</li>
<li><p>rewrite 原理，如何实现重写</p>
<blockquote>
<p>AOF 文件持续增长而过大时，会 fork 出一条新进程来将文件重写（也是先写临时文件最后再 rename）</p>
<p>redis4.0 版本后的重写，实质上就是把 rdb 的快照，以二级制的形式附在新的 aof 头部，作为已有的历史数据，替换掉原来的流水账操作</p>
</blockquote>
<blockquote>
<p><code>no-appendfsync-on-rewrite=yes</code></p>
<p>不写入 aof 文件只写入缓存，用户请求不会阻塞，但是在这段时间如果宕机会丢失这段时间的缓存数据（降低数据安全性，提高性能）</p>
<p><code>no-appendfsync-on-rewrite=no</code></p>
<p>把数据往磁盘里刷，但是遇到重写操作，可能会发生阻塞（数据安全，但是性能降低）</p>
</blockquote>
</li>
<li><p>触发机制，何时重写</p>
<blockquote>
<p>Redis 会记录上次重写时的 AOF 大小，默认配置是当 AOF 文件大小是上次 rewrite 后大小的一倍且文件大于 64M 时触发</p>
<p>重写虽然可以节约大量磁盘空间，减少恢复时间</p>
<p>但是每次重写还是有一定的负担的，因此设定 Redis 要满足一定条件才会进行重写</p>
<ul>
<li><code>auto-aof-rewrite-percentage</code>：设置重写的基准值，文件达到 100% 时开始重写（文件是原来重写后文件的 2 倍时触发）</li>
<li><code>auto-aof-rewrite-min-size</code>：设置重写的基准值，最小文件64MB。达到这个值开始重写</li>
</ul>
<p>例如：文件达到70MB开始重写，降到50MB，下次什么时候开始重写？100MB</p>
<p>系统载入时或者上次重写完毕时，Redis 会记录此时 AOF 大小，设为 base_size,</p>
<p>如果 Redis 的 AOF 当前大小 &gt;&#x3D; <code>base_size + base_size * 100%</code> （默认）且当前大小&gt;&#x3D;64mb（默认）的情况下，Redis 会对 AOF 进行重写</p>
</blockquote>
</li>
<li><p>重写流程</p>
<ul>
<li><code>bgrewriteaof</code> 触发重写，判断是否当前有 <code>bgsave</code> 或 <code>bgrewriteaof</code> 在运行，如果有则等待该命令结束后再继续执行</li>
<li>主进程 fork 出子进程执行重写操作，保证主进程不会阻塞</li>
<li>子进程遍历 redis 内存中数据到临时文件，客户端的写请求同时写入 aof_buf 缓冲区和 aof_rewrite_buf 重写缓冲区保证原 AOF 文件完整以及新 AOF 文件生成期间的新的数据修改动作不会丢失</li>
<li>子进程写完新的 AOF 文件后，向主进程发信号，父进程更新统计信息</li>
<li>主进程把 <code>aof_rewrite_buf</code> 中的数据写入到新的 AOF 文件</li>
<li>使用新的 AOF 文件覆盖旧的 AOF 文件，完成 AOF 重写</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013152135263.png" alt="image-20221013152135263"></p>
</li>
</ul>
</li>
<li><p>AOF 总结</p>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/AOF%E6%80%BB%E7%BB%93.jpg" alt="AOF总结"></p>
<ul>
<li>AOF 优势<ul>
<li>备份机制更稳健，丢失数据概率更低</li>
<li>可读的日志文本，通过操作 AOF 稳健，可以处理误操作</li>
</ul>
</li>
<li>AOF 劣势<ul>
<li>比起 RDB 占用更多的磁盘空间</li>
<li>恢复备份速度要慢</li>
<li>每次读写都同步的话，有一定的性能压力</li>
<li>存在个别 Bug，造成恢复不能</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>RDB vs AOF</strong> </p>
<ul>
<li><p>RDB 和 AOF 同时开启，redis 听谁的？</p>
<blockquote>
<p>AOF 和 RDB 同时开启，系统默认取 AOF 的数据（数据不会存在丢失）</p>
</blockquote>
</li>
<li><p>RDB 和 AOF 用哪个好</p>
<blockquote>
<p><strong>官方推荐两个都启用</strong></p>
<p><strong>如果对数据不敏感，可以选单独用RDB</strong></p>
<p><strong>不建议单独用 AOF，因为可能会出现Bug</strong></p>
<p><strong>如果只是做纯内存缓存，可以都不用</strong></p>
</blockquote>
</li>
<li><p>官方建议</p>
<ul>
<li><p>RDB 持久化方式能够在指定的时间间隔能对你的数据进行快照存储</p>
</li>
<li><p>AOF 持久化方式记录每次对服务器写的操作，当服务器重启的时候会重新执行这些命令来恢复原始的数据，AOF 命令以 redis 协议追加保存每次写的操作到文件末尾</p>
</li>
<li><p>Redis 还能对 AOF 文件进行后台重写，使得 AOF 文件的体积不至于过大</p>
</li>
<li><p>只做缓存：如果只希望数据在服务器运行的时候存在，也可以不使用任何持久化方式.</p>
</li>
<li><p>同时开启两种持久化方式</p>
</li>
<li><p>在这种情况下,当 redis 重启的时候会优先载入 AOF 文件来恢复原始的数据,，因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整</p>
</li>
<li><p>RDB 的数据不实时，同时使用两者时服务器重启也只会找 AOF 文件。那要不要只使用AOF呢？ </p>
<blockquote>
<p>建议不要，因为 RDB 更适合用于备份数据库（AOF在不断变化不好备份）， 快速重启，而且不会有 AOF 可能潜在的 bug，留着作为一个万一的手段</p>
</blockquote>
</li>
<li><p>性能建议</p>
<blockquote>
<p>因为 RDB 文件只用作后备用途，建议只在 Slave 上持久化 RDB 文件，而且只要 15 分钟备份一次就够了，只保留 save 900 1 这条规则</p>
<p>如果使用 AOF，好处是在最恶劣情况下也只会丢失不超过两秒数据，启动脚本较简单只 load 自己的 AOF 文件就可以了</p>
<p>代价：</p>
<ul>
<li>带来了持续的 IO</li>
<li>AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的</li>
</ul>
<p>只要硬盘许可，应该尽量减少 AOF rewrite 的频率，AOF 重写的基础大小默认值 64M太小了，可以设到 5G 以上</p>
<p>默认超过原大小 100% 大小时重写可以改到适当的数值</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="12-Redis6-的主从复制"><a href="#12-Redis6-的主从复制" class="headerlink" title="12.Redis6 的主从复制"></a>12.Redis6 的主从复制</h3><ol>
<li><p>什么是主从复制</p>
<blockquote>
<p>主机数据更新后根据配置和策略， 自动同步到备机的 <code>master/slaver</code> 机制</p>
<p><strong>Master以写为主，Slave以读为主</strong></p>
</blockquote>
</li>
<li><p>主从复制的作用</p>
<ul>
<li><p>读写分离，性能扩展</p>
</li>
<li><p>容灾快速恢复</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6%E4%BD%9C%E7%94%A8.jpg" alt="主从复制作用"></p>
</li>
</ul>
</li>
<li><p>实例</p>
<blockquote>
<p>拷贝多个 <code>redis.conf</code> 文件 <code>include</code>（写绝对路径）</p>
<p>开启 <code>daemonize yes</code></p>
<p><code>Pid</code> 文件名字 <code>pidfile</code></p>
<p>指定端口 <code>port</code></p>
<p><code>Log</code> 文件名字</p>
<p><code>dump.rdb</code> 名字 <code>dbfilename</code></p>
<p><code>Appendonly</code> 关掉或者换名字</p>
</blockquote>
<blockquote>
<p>以下三项其中 <code>include /myredis/redis.conf</code> 需要修改为 <code>include redis.conf</code></p>
<p><strong>slave-priority 10</strong></p>
<p><strong>设置从机的优先级，值越小，优先级越高，用于选举主机时使用。默认100</strong></p>
</blockquote>
<ul>
<li><p>新建 <code>redis6379.conf</code></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013163737021.png" alt="image-20221013163737021"></p>
</li>
<li><p>新建 <code>redis6380.conf</code></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013163805045.png" alt="image-20221013163805045"></p>
</li>
<li><p>新建 <code>redis6381.conf</code></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013163812892.png" alt="image-20221013163812892"></p>
</li>
<li><p>启动三台 redis 服务器</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013163959687.png" alt="image-20221013163959687"></p>
</li>
<li><p>查看系统进程</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164017069.png" alt="image-20221013164017069"></p>
</li>
<li><p>查看三台主机运行情况</p>
<blockquote>
<p><code>info replication</code>：打印主从复制的相关信息</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164106856.png" alt="image-20221013164106856"></p>
</li>
<li><p>配从（库）不配主（库）</p>
<blockquote>
<p><code>slaveof &lt;ip&gt; &lt;port&gt;</code>：成为某个实例的从服务器</p>
</blockquote>
</li>
<li><p>在 6380 和 6381上执行 <code>slaveof 127.0.0.1 6379</code></p>
</li>
<li><p>在主机上些，在从机可以读取数据</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164221467.png" alt="image-20221013164221467"></p>
</li>
<li><p>在从机写报错</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164244565.png" alt="image-20221013164244565"></p>
</li>
<li><p>主机挂掉，重启就行，一切如初</p>
</li>
<li><p>从机重启需重设：<code>slaveof 127.0.0.1 6379</code></p>
<blockquote>
<p>可以将配置增加到文件中。永久生效</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164307526.png" alt="image-20221013164307526"></p>
</li>
</ul>
</li>
<li><p>常用主从复制</p>
<ul>
<li><p>一主二仆</p>
<blockquote>
<p>切入点问题？slave1、slave2 是从头开始复制还是从切入点开始复制?</p>
<p>比如从 k4 进来，那之前的 k1,k2,k3 是否也可以复制？</p>
<p>Answer：从头开始复制，k1、k2、k3、k4 都有</p>
<hr>
<p>从机是否可以写？set可否？ </p>
<p>Answer：主写从读</p>
<hr>
<p>主机 shutdown 后情况如何？从机是上位还是原地待命？</p>
<p>Answer：从机知道主机 shutdown，但是还是保持从机身份</p>
<hr>
<p>主机又回来了后，主机新增记录，从机还能否顺利复制？ </p>
<p>Answer：可以</p>
<hr>
<p>其中一台从机 shutdown 后情况如何？重新开机后它能跟上大部队吗？</p>
<p>Answer：可以</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164417231.png" alt="image-20221013164417231"></p>
</li>
<li><p>薪火相传</p>
<blockquote>
<p>上一个 Slave 可以是下一个 slave 的 Master，Slave 同样可以接收其他 slaves 的连接和同步请求，那么该 slave 作为了链条中下一个的 master, 可以有效减轻 master 的写压力，去中心化降低风险</p>
<p>用 <code>slaveof &lt;ip&gt; &lt;port&gt;</code></p>
<p>中途变更转向：会清除之前的数据，重新建立拷贝最新的</p>
<p>风险是一旦某个 slave 宕机，后面的 slave 都没法备份</p>
<p>主机挂了，从机还是从机，无法写数据了</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164503982.png" alt="image-20221013164503982"></p>
</li>
<li><p>反客为主</p>
<blockquote>
<p>当一个 master 宕机后，后面的 slave 可以立刻升为 master，其后面的 slave 不用做任何修改</p>
<p>用 <code>slaveof no one</code> 将从机变为主机</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164532310.png" alt="image-20221013164532310"></p>
</li>
</ul>
</li>
<li><p>复制原理</p>
<ul>
<li>Slave 启动成功连接到 master 后会发送一个 sync 命令</li>
<li>Master 接到命令启动后台的存盘进程，同时收集所有接收到的用于修改数据集命令， 在后台进程执行完毕之后，master 将传送整个数据文件到 slave，以完成一次完全同步</li>
<li>全量复制：而 slave 服务在接收到数据库文件数据后，将其存盘并加载到内存中</li>
<li>增量复制：Master 继续将新的所有收集到的修改命令依次传给 slave，完成同步</li>
<li>但是只要是重新连接 master，一次完全同步（全量复制）将被自动执行</li>
</ul>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E5%A4%8D%E5%88%B6%E5%8E%9F%E7%90%86.jpg" alt="复制原理"></p>
</li>
<li><p>哨兵模式</p>
<ul>
<li><p>定义：反客为主的自动版，能够后台监控主机是否故障，如果故障了根据投票数自动将从库转换为主库</p>
</li>
<li><p>使用步骤</p>
<ul>
<li><p>原为一主二仆模式，6379 带着 6380、6381</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013164851667.png" alt="image-20221013164851667"></p>
</li>
<li><p>自定义的 <code>/myredis</code> 目录下新建 <code>sentinel.conf</code> 文件，名字绝不能错</p>
<blockquote>
<p><code>sentinel monitor mymaster 127.0.0.1 6379 1</code></p>
<p>其中 mymaster 为监控对象起的服务器名称， 1 为至少有多少个哨兵同意迁移的数量</p>
</blockquote>
</li>
<li><p>启动哨兵</p>
<blockquote>
<p><code>/usr/local/bin</code></p>
<p>redis 做压测可以用自带的 <code>redis-benchmark</code> 工具</p>
<p>执行 <code>redis-sentinel /myredis/sentinel.conf</code></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013165010191.png" alt="image-20221013165010191"></p>
</li>
<li><p>当主机挂掉，从机选举中产生新的主机</p>
<blockquote>
<p>(大概10秒左右可以看到哨兵窗口日志，切换了新的主机)</p>
<p>哪个从机会被选举为主机呢？根据优先级别：slave-priority </p>
<p>原主机重启后会变为从机</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013165042799.png" alt="image-20221013165042799"></p>
</li>
<li><p>复制延时</p>
<blockquote>
<p>由于所有的写操作都是先在 Master 上操作，然后同步更新到 Slave 上，所以从 Master 同步到 Slave 机器有一定的延迟</p>
<p>当系统很繁忙的时候，延迟问题会更加严重，Slave 机器数量的增加也会使这个问题更加严重</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>故障恢复</p>
<blockquote>
<p>优先级在 <code>redis.conf</code> 中默认：<code>slave-priority 100</code>，<strong>值越小优先级越高</strong></p>
<p>偏移量是指获得原主机数据最全的</p>
<p>每个 redis 实例启动后都会随机生成一个 40 位的 runid</p>
</blockquote>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E6%95%85%E9%9A%9C%E6%81%A2%E5%A4%8D.jpg" alt="故障恢复"></p>
</li>
<li><p>主从复制 Jedis 实现</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> JedisSentinelPool jedisSentinelPool=<span class="literal">null</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> Jedis <span class="title function_">getJedisFromSentinel</span><span class="params">()</span>&#123;</span><br><span class="line">	<span class="keyword">if</span>(jedisSentinelPool==<span class="literal">null</span>)&#123;</span><br><span class="line">        Set&lt;String&gt; sentinelSet=<span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;&gt;();</span><br><span class="line">        sentinelSet.add(<span class="string">&quot;192.168.11.103:26379&quot;</span>);</span><br><span class="line"></span><br><span class="line">        <span class="type">JedisPoolConfig</span> <span class="variable">jedisPoolConfig</span> <span class="operator">=</span><span class="keyword">new</span> <span class="title class_">JedisPoolConfig</span>();</span><br><span class="line">        jedisPoolConfig.setMaxTotal(<span class="number">10</span>); <span class="comment">//最大可用连接数</span></span><br><span class="line">        jedisPoolConfig.setMaxIdle(<span class="number">5</span>); <span class="comment">//最大闲置连接数</span></span><br><span class="line">        jedisPoolConfig.setMinIdle(<span class="number">5</span>); <span class="comment">//最小闲置连接数</span></span><br><span class="line">        jedisPoolConfig.setBlockWhenExhausted(<span class="literal">true</span>); <span class="comment">//连接耗尽是否等待</span></span><br><span class="line">        jedisPoolConfig.setMaxWaitMillis(<span class="number">2000</span>); <span class="comment">//等待时间</span></span><br><span class="line">        jedisPoolConfig.setTestOnBorrow(<span class="literal">true</span>); <span class="comment">//取连接的时候进行一下测试 ping pong</span></span><br><span class="line"></span><br><span class="line">        jedisSentinelPool=<span class="keyword">new</span> <span class="title class_">JedisSentinelPool</span>(<span class="string">&quot;mymaster&quot;</span>,sentinelSet,jedisPoolConfig);</span><br><span class="line">        <span class="keyword">return</span> jedisSentinelPool.getResource();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">		<span class="keyword">return</span> jedisSentinelPool.getResource();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li>
</ol>
<h3 id="13-Redis6-集群"><a href="#13-Redis6-集群" class="headerlink" title="13.Redis6 集群"></a>13.Redis6 集群</h3><ol>
<li><p>存在问题</p>
<blockquote>
<p>容量不够，redis 如何进行扩容？</p>
<p>并发写操作， redis 如何分摊？</p>
<p>另外，主从模式，薪火相传模式，主机宕机，导致 ip 地址发生变化，应用程序中配置需要修改对应的主机地址、端口等信息</p>
<p>之前通过代理主机来解决，但是 redis3.0 中提供了解决方案。就是<strong>无中心化集群配置</strong></p>
</blockquote>
</li>
<li><p>集群定义</p>
<blockquote>
<p>Redis 集群实现了对 Redis 的水平扩容，即启动 N 个 redis 节点，将整个数据库分布存储在这 N 个节点中，每个节点存储总数据的 1 &#x2F; N</p>
<p>Redis 集群通过分区（partition）来提供一定程度的可用性（availability）： 即使集群中有一部分节点失效或者无法进行通讯， 集群也可以继续处理命令请求</p>
</blockquote>
</li>
<li><p>删除持久化数据</p>
<blockquote>
<p>将 <code>rdb、aof</code> 文件都删除掉</p>
</blockquote>
</li>
<li><p>搭建集群实例</p>
<blockquote>
<p>制作 6 个实例：6379,6380,6381,6389,6390,6391</p>
</blockquote>
<ul>
<li><p>配置基本信息</p>
<blockquote>
<p>开启 <code>daemonize yes</code></p>
<p><code>Pid</code> 文件名字</p>
<p>指定端口</p>
<p><code>Log</code> 文件名字</p>
<p><code>Dump.rdb</code> 名字</p>
<p><code>Appendonly</code> 关掉或者换名字</p>
</blockquote>
</li>
<li><p><code>redis cluster</code> 配置修改</p>
<blockquote>
<p><code>cluster-enabled yes</code>：打开集群模式</p>
<p><code>cluster-config-file nodes-6379.conf</code>：设定节点配置文件名</p>
<p><code>cluster-node-timeout 15000</code>：设定节点失联时间，超过该时间（毫秒），集群自动进行主从切换</p>
</blockquote>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line">include /home/bigdata/redis.conf</span><br><span class="line">port 6379</span><br><span class="line">pidfile &quot;/var/run/redis_6379.pid&quot;</span><br><span class="line">dbfilename &quot;dump6379.rdb&quot;</span><br><span class="line">dir &quot;/home/bigdata/redis_cluster&quot;</span><br><span class="line">logfile &quot;/home/bigdata/redis_cluster/redis_err_6379.log&quot;</span><br><span class="line">cluster-enabled yes</span><br><span class="line">cluster-config-file nodes-6379.conf</span><br><span class="line">cluster-node-timeout 15000</span><br></pre></td></tr></table></figure>
</li>
<li><p>将配置文件拷贝多份，并修改对应端口号</p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013193611578.png" alt="image-20221013193611578"></p>
</li>
<li><p>启动 6 个 redis 服务</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013193640717.png" alt="image-20221013193640717"></p>
</li>
<li><p>将 6 个节点合成一个集群</p>
<blockquote>
<p>组合之前，请确保所有 redis 实例启动后，<code>nodes-xxxx.conf</code> 文件都生成正常</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013193714776.png" alt="image-20221013193714776"></p>
<figure class="highlight shell"><table><tr><td class="code"><pre><span class="line">cd /opt/module/redis-6.2.1/src</span><br><span class="line"></span><br><span class="line">redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379 192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389 192.168.11.101:6390 192.168.11.101:6391</span><br></pre></td></tr></table></figure>

<blockquote>
<p>此处不要用 127.0.0.1， 请用真实 IP 地址</p>
<p>–replicas 1 采用最简单的方式配置集群，一台主机，一台从机，正好三组</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013193811851.png" alt="image-20221013193811851"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013193828311.png" alt="image-20221013193828311"></p>
<blockquote>
<p>普通方式登录可能直接进入读主机，存储数据时，会出现 MOVED 重定向操作</p>
<p>所以，应该以集群方式登录</p>
</blockquote>
</li>
<li><p>采用集群策略连接，设置数据会自动切换到相应的写主机</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013193934565.png" alt="image-20221013193934565"></p>
</li>
<li><p>通过 cluster nodes 命令查看集群信息</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013193957067.png" alt="image-20221013193957067"></p>
</li>
<li><p>redis cluster 如何分配这六个节点?</p>
<blockquote>
<p>一个集群至少要有三个主节点</p>
<p>选项 <code>--cluster-replicas 1</code> 表示我们希望为集群中的每个主节点创建一个从节点</p>
<p>分配原则尽量保证每个主数据库运行在不同的IP地址，每个从库和主库不在一个IP地址上</p>
</blockquote>
</li>
<li><p>什么是 slots</p>
<blockquote>
<p>一个 Redis 集群包含 16384 个插槽（hash slot）， 数据库中的每个键都属于这 16384 个插槽的其中一个，</p>
<p>集群使用公式 <code>CRC16(key) % 16384</code> 来计算键 key 属于哪个槽， 其中 <code>CRC16(key)</code> 语句用于计算键 key 的 CRC16 校验和</p>
<p>集群中的每个节点负责处理一部分插槽</p>
<p>举个例子， 如果一个集群可以有主节点， 其中：</p>
<ul>
<li><p>节点 A 负责处理 0 号至 5460 号插槽</p>
</li>
<li><p>节点 B 负责处理 5461 号至 10922 号插槽</p>
</li>
<li><p>节点 C 负责处理 10923 号至 16383 号插槽</p>
</li>
</ul>
</blockquote>
</li>
</ul>
</li>
<li><p>集群使用实例</p>
<ul>
<li><p>在集群中录入值</p>
<blockquote>
<p>在 redis-cli 每次录入、查询键值，redis 都会计算出该 key 应该送往的插槽，如果不是该客户端对应服务器的插槽，redis 会报错，并告知应前往的redis实例地址和端口</p>
<p>redis-cli 客户端提供了 –c 参数实现自动重定向</p>
<p>如 <code>redis-cli  -c –p 6379</code> 登入后，再录入、查询键值对可以自动重定向</p>
<p>不在一个slot下的键值，是不能使用 mget,mset 等多键操作</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013194305963.png" alt="image-20221013194305963"></p>
<p>可以通过 <code>&#123;&#125;</code> 来定义组的概念，从而使 <code>key</code> 中 <code>&#123;&#125;</code> 内相同内容的键值对放到一个 slot 中去</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013194330652.png" alt="image-20221013194330652"></p>
</blockquote>
</li>
<li><p>查询集群中的值</p>
<blockquote>
<p><code>CLUSTER GETKEYSINSLOT &lt;slot&gt; &lt;count&gt;</code>：返回 count 个 slot 槽中的键</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013194408303.png" alt="image-20221013194408303"></p>
</blockquote>
</li>
<li><p>故障恢复</p>
<blockquote>
<p>如果主节点下线？从节点能否自动升为主节点？注意：<strong>15 秒超时</strong></p>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E6%95%85%E9%9A%9C%E6%81%A2%E5%A4%8D1.jpg" alt="故障恢复1"></p>
<p>主节点恢复后，主从关系会如何？主节点回来变成从机</p>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E6%95%85%E9%9A%9C%E6%81%A2%E5%A4%8D2.jpg" alt="故障恢复2"></p>
<p>如果所有某一段插槽的主从节点都宕掉，redis 服务是否还能继续?</p>
<p>如果某一段插槽的主从都挂掉，而 <code>cluster-require-full-coverage=yes</code> ，那么整个集群都挂掉</p>
<p>如果某一段插槽的主从都挂掉，而 <code>cluster-require-full-coverage=no</code> ，那么该插槽数据全都不能使用，也无法存储</p>
<p>redis.conf 中的参数 <code>cluster-require-full-coverage</code></p>
</blockquote>
</li>
</ul>
</li>
<li><p>集群的 Jedis 开发</p>
<blockquote>
<p>即使连接的不是主机，集群会自动切换主机存储</p>
<p><strong>主机写，从机读</strong></p>
<p><strong>无中心化主从集群</strong></p>
<p>无论从哪台主机写的数据，其他主机上都能读到数据</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">JedisClusterTest</span> &#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123; </span><br><span class="line">        Set&lt;HostAndPort&gt;set =<span class="keyword">new</span> <span class="title class_">HashSet</span>&lt;HostAndPort&gt;();</span><br><span class="line">        set.add(<span class="keyword">new</span> <span class="title class_">HostAndPort</span>(<span class="string">&quot;192.168.10.120&quot;</span>,<span class="number">6379</span>));</span><br><span class="line">        JedisCluster jedisCluster=<span class="keyword">new</span> <span class="title class_">JedisCluster</span>(set);</span><br><span class="line">        jedisCluster.set(<span class="string">&quot;k1&quot;</span>, <span class="string">&quot;v1&quot;</span>);</span><br><span class="line">        System.out.println(jedisCluster.get(<span class="string">&quot;k1&quot;</span>));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>
</li>
<li><p>Redis 集群总结</p>
<ul>
<li>集群优点<ul>
<li>实现扩容</li>
<li>分摊压力</li>
<li>无中心配置相对简单</li>
</ul>
</li>
<li>集群缺点<ul>
<li>多键操作是不被支持的 </li>
<li>多键的 Redis 事务是不被支持的。lua 脚本不被支持</li>
<li>由于集群方案出现较晚，很多公司已经采用了其他的集群方案，而代理或者客户端分片的方案想要迁移至 redis cluster，需要整体迁移而不是逐步过渡，复杂度较大</li>
</ul>
</li>
</ul>
</li>
</ol>
<h3 id="14-Redis6-应用问题解决"><a href="#14-Redis6-应用问题解决" class="headerlink" title="14.Redis6 应用问题解决"></a>14.Redis6 应用问题解决</h3><ol>
<li><p>缓存穿透</p>
<ul>
<li><p>问题描述</p>
<blockquote>
<p>key 对应的数据在数据源并不存在，每次针对此 key 的请求从缓存获取不到，请求都会压到数据源，从而可能压垮数据源</p>
<p>比如用一个不存在的用户 id 获取用户信息，不论缓存还是数据库都没有，若黑客利用此漏洞进行攻击可能压垮数据库</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E7%BC%93%E5%AD%98%E7%A9%BF%E9%80%8F.png" alt="缓存穿透"></p>
</li>
<li><p>解决方案</p>
<blockquote>
<p>一个一定不存在缓存及查询不到的数据，由于缓存是不命中时被动写的，并且出于容错考虑，如果从存储层查不到数据则不写入缓存，这将导致这个不存在的数据每次请求都要到存储层去查询，失去了缓存的意义</p>
<p>解决方案如下：</p>
</blockquote>
<ul>
<li><p><strong>对空值缓存</strong></p>
<blockquote>
<p>如果一个查询返回的数据为空（不管是数据是否不存在），我们仍然把这个空结果（null）进行缓存，设置空结果的过期时间会很短，最长不超过五分钟</p>
</blockquote>
</li>
<li><p><strong>设置可访问的名单（白名单）</strong></p>
<blockquote>
<p>使用 bitmaps 类型定义一个可以访问的名单，名单 id 作为 bitmaps 的偏移量，每次访问和 bitmap 里面的 id 进行比较，如果访问 id 不在 bitmaps 里面，则进行拦截，不允许访问</p>
</blockquote>
</li>
<li><p><strong>采用布隆过滤器</strong></p>
<blockquote>
<p>布隆过滤器（Bloom Filter）是1970年由布隆提出的</p>
<p>它实际上是一个很长的二进制向量（位图）和一系列随机映射函数（哈希函数）</p>
<p>布隆过滤器可以用于检索一个元素是否在一个集合中</p>
<p>它的优点是空间效率和查询时间都远远超过一般的算法，缺点是有一定的误识别率和删除困难</p>
<p>将所有可能存在的数据哈希到一个足够大的 bitmaps 中，一个一定不存在的数据会被 这个 bitmaps 拦截掉，从而避免了对底层存储系统的查询压力</p>
</blockquote>
</li>
<li><p><strong>进行实时监控</strong></p>
<blockquote>
<p>当发现 Redis 的命中率开始急速降低，需要排查访问对象和访问的数据，和运维人员配合，可以设置黑名单限制服务</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>缓存击穿</p>
<ul>
<li><p>问题描述</p>
<blockquote>
<p>key 对应的数据存在，但在 redis 中过期，此时若有大量并发请求过来，这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端 DB 压垮</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E7%BC%93%E5%AD%98%E5%87%BB%E7%A9%BF.png" alt="缓存击穿"></p>
</li>
<li><p>解决方案</p>
<blockquote>
<p>key 可能会在某些时间点被超高并发地访问，是一种非常<code>热点</code>的数据</p>
<p>需要考虑缓存被<code>击穿</code>的问题</p>
<p>解决方案如下：</p>
</blockquote>
<ul>
<li><p><strong>预先设置热门数据</strong></p>
<blockquote>
<p>在 redis 高峰访问之前，把一些热门数据提前存入到 redis 里面，加大这些热门数据 key 的时长</p>
</blockquote>
</li>
<li><p><strong>实时调整</strong></p>
<blockquote>
<p>现场监控哪些数据热门，实时调整 key 的过期时长</p>
</blockquote>
</li>
<li><p><strong>使用锁</strong></p>
<blockquote>
<p>就是在缓存失效的时候（判断拿出来的值为空），不是立即去 <code>load db</code></p>
<p>先使用缓存工具的某些带成功操作返回值的操作（比如 Redis 的 SETNX）去 set 一个 mutex key</p>
<p>当操作返回成功时，再进行 load db 的操作，并回设缓存，最后删除 mutex key</p>
<p>当操作返回失败，证明有线程在 load db，当前线程睡眠一段时间再重试整个 get 缓存的方法</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E9%94%81.png" alt="锁"></p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>缓存雪崩</p>
<ul>
<li><p>问题描述</p>
<blockquote>
<p>key 对应的数据存在，但在 redis 中过期，此时若有大量并发请求过来，这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存，这个时候大并发的请求可能会瞬间把后端 DB 压垮</p>
<p><strong>缓存雪崩与缓存击穿的区别在于这里针对很多 key 缓存，前者则是某一个 key 正常访问</strong></p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/%E7%BC%93%E5%AD%98%E9%9B%AA%E5%B4%A9.png" alt="缓存雪崩"></p>
</li>
<li><p>解决方案</p>
<blockquote>
<p>缓存失效时的雪崩效应对底层系统的冲击非常可怕！</p>
<p>解决方案如下：</p>
</blockquote>
<ul>
<li><p><strong>构建多级缓存架构</strong></p>
<blockquote>
<p>nginx 缓存 + redis 缓存 + 其他缓存（ehcache 等）</p>
<p>对于集群部署的情况，将热点数据均与分布在不同缓存中</p>
</blockquote>
</li>
<li><p><strong>使用锁或队列</strong></p>
<blockquote>
<p>用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写，从而避免失效时大量的并发请求落到底层存储系统上</p>
<p>不适用高并发情况</p>
</blockquote>
</li>
<li><p><strong>设置过期标志更新缓存</strong></p>
<blockquote>
<p>记录缓存数据是否过期（设置提前量），如果过期会触发通知另外的线程在后台去更新实际 key 的缓存</p>
</blockquote>
</li>
<li><p><strong>将缓存失效时间分散开</strong></p>
<blockquote>
<p>比如我们可以在原有的失效时间基础上增加一个随机值，比如 1-5 分钟随机，这样每一个缓存的过期时间的重复率就会降低，就很难引发集体失效的事件</p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>分布式锁</p>
<ul>
<li><p>问题描述</p>
<blockquote>
<p>随着业务发展的需要，原单体单机部署的系统被演化成分布式集群系统后，由于分布式系统多线程、多进程并且分布在不同机器上，这将使原单机部署情况下的并发控制锁策略失效，单纯的 Java API 并不能提供分布式锁的能力</p>
<p>为了解决这个问题就需要一种跨 JVM 的互斥机制来控制共享资源的访问，这就是分布式锁要解决的问题</p>
<p>分布式锁主流的实现方案如下：</p>
</blockquote>
<ul>
<li>基于数据库实现分布式锁</li>
<li>基于缓存（Redis 等）：性能最高</li>
<li>基于 Zookeeper：可靠性最高</li>
</ul>
</li>
<li><p>解决方案：基于缓存，使用 redis 实现分布式锁</p>
<blockquote>
<p>redis 命令：<code>set sku:1:info “OK” NX PX 10000</code></p>
<p><code>EX second</code>：设置键的过期时间为 second 秒。 <code>SET key value EX second</code> 效果等同于 <code>SETEX key second value</code></p>
<p><code>PX millisecond</code>：设置键的过期时间为 millisecond 毫秒。 <code>SET key value PX millisecond</code> 效果等同于 <code>PSETEX key millisecond value</code> </p>
<p><code>NX</code>：只在键不存在时，才对键进行设置操作。 <code>SET key value NX</code> 效果等同于 <code>SETNX key value </code></p>
<p><code>XX</code>：只在键已经存在时，才对键进行设置操作</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013200443170.png" alt="image-20221013200443170"></p>
<blockquote>
<p>多个客户端同时获取锁（setnx）</p>
<p>获取成功，执行业务逻辑 {从 db 获取数据，放入缓存}，执行完成释放锁（del）</p>
<p>其他客户端等待重试</p>
</blockquote>
</li>
<li><p>代码实现</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="meta">@GetMapping(&quot;testLock&quot;)</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testLock</span><span class="params">()</span>&#123;</span><br><span class="line">    <span class="comment">//1获取锁，setne</span></span><br><span class="line">    <span class="type">Boolean</span> <span class="variable">lock</span> <span class="operator">=</span> redisTemplate.opsForValue().setIfAbsent(<span class="string">&quot;lock&quot;</span>, <span class="string">&quot;111&quot;</span>);</span><br><span class="line">    <span class="comment">//2获取锁成功、查询num的值</span></span><br><span class="line">    <span class="keyword">if</span>(lock)&#123;</span><br><span class="line">        <span class="type">Object</span> <span class="variable">value</span> <span class="operator">=</span> redisTemplate.opsForValue().get(<span class="string">&quot;num&quot;</span>);</span><br><span class="line">        <span class="comment">//2.1判断num为空return</span></span><br><span class="line">        <span class="keyword">if</span>(StringUtils.isEmpty(value))&#123;</span><br><span class="line">            <span class="keyword">return</span>;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="comment">//2.2有值就转成成int</span></span><br><span class="line">        <span class="type">int</span> <span class="variable">num</span> <span class="operator">=</span> Integer.parseInt(value+<span class="string">&quot;&quot;</span>);</span><br><span class="line">        <span class="comment">//2.3把redis的num加1</span></span><br><span class="line">        redisTemplate.opsForValue().set(<span class="string">&quot;num&quot;</span>, ++num);</span><br><span class="line">        <span class="comment">//2.4释放锁，del</span></span><br><span class="line">        redisTemplate.delete(<span class="string">&quot;lock&quot;</span>);</span><br><span class="line"></span><br><span class="line">    &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">        <span class="comment">//3获取锁失败、每隔0.1秒再获取</span></span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            Thread.sleep(<span class="number">100</span>);</span><br><span class="line">            testLock();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p>优化之设置锁的过期时间</p>
<blockquote>
<p>设置过期时间有两种方式：</p>
<ul>
<li>首先想到通过 expire 设置过期时间（缺乏原子性：如果在 setnx 和 expire 之间出现异常，锁也无法释放）</li>
<li>在 set 时指定过期时间（推荐）</li>
</ul>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013200703019.png" alt="image-20221013200703019"></p>
</blockquote>
</li>
<li><p>优化之 UUID 防误删</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013200758376.png" alt="image-20221013200758376"></p>
</li>
<li><p>优化之 LUA 脚本保证删除的原子性</p>
</li>
</ul>
</li>
</ol>
<h3 id="15-Reids6-新功能"><a href="#15-Reids6-新功能" class="headerlink" title="15.Reids6 新功能"></a>15.Reids6 新功能</h3><ol>
<li><p>ACL</p>
<ul>
<li><p>定义</p>
<blockquote>
<p>Redis ACL 是 Access Control List（访问控制列表）的缩写，该功能允许根据可以执行的命令和可以访问的键来限制某些连接</p>
<p>在 Redis5 版本之前，Redis 安全规则只有密码控制 还有通过 rename 来调整高危命令比如 <code>flushdb ， KEYS* ， shutdown</code> 等</p>
<p>Redis6 则提供 ACL 的功能对用户进行更细粒度的权限控制 ：</p>
<ul>
<li>接入权限：用户名和密码 </li>
<li>可以执行的命令</li>
<li>可以操作的 KEY</li>
</ul>
</blockquote>
</li>
<li><p>常用命令</p>
<ul>
<li><p>使用 <code>acl list</code> 命令展现用户权限列表</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201100070.png" alt="image-20221013201100070"></p>
</li>
<li><p>使用 <code>acl cat</code> 命令查看添加权限指令类别</p>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201143029.png" alt="image-20221013201143029"></p>
</li>
<li><p>使用 <code>acl cat</code> 加参数类型名可以查看类型下具体命令</p>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201149589.png" alt="image-20221013201149589"></p>
</li>
<li><p>使用 <code>acl whoami</code> 命令查看当前用户</p>
<p>  <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201219083.png" alt="image-20221013201219083"></p>
</li>
<li><p>使用 <code>aclsetuser</code> 命令创建和编辑用户 <code>ACL</code></p>
<blockquote>
<p>ACL 规则</p>
<p>某些规则只是用于激活或删除标志，或对用户 ACL 执行给定更改的单个单词</p>
<p>其他规则是字符前缀，它们与命令或类别名称、键模式等连接在一起</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/ACL%E8%A7%84%E5%88%991.png" alt="ACL规则1"></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/ACL%E8%A7%84%E5%88%992.png" alt="ACL规则2"></p>
</blockquote>
<blockquote>
<p>通过命令创建新用户默认权限</p>
<p><code>acl setuser user1</code></p>
<p> <img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201456626.png" alt="image-20221013201456626"></p>
<p>在上面的示例中没有指定任何规则</p>
<p>如果用户不存在，这将使用 just created 的默认属性来创建用户</p>
<p>如果用户已经存在，则上面的命令将不执行任何操作</p>
</blockquote>
<blockquote>
<p>设置有用户名、密码、ACL权限、并启用的用户</p>
<p><code>acl setuser user2 on &gt;password ~cached:* +get</code></p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201538905.png" alt="image-20221013201538905"></p>
</blockquote>
<blockquote>
<p>切换用户，验证权限</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201551370.png" alt="image-20221013201551370"></p>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p>IO 多线程</p>
<ul>
<li><p>简介</p>
<blockquote>
<p>Redis6终于支撑多线程了，告别单线程了吗？</p>
<p>IO 多线程其实指<strong>客户端交互部分</strong>的<strong>网络 IO <strong>交互处理模块</strong>多线程</strong>，而非<strong>执行命令多线程</strong></p>
<p>Redis6 执行命令依然是单线程</p>
</blockquote>
</li>
<li><p>原理架构</p>
<blockquote>
<p>Redis6 加入多线程，但跟 Memcached 这种从 IO 处理到数据访问多线程的实现模式有些差异</p>
<p><strong>Redis 的多线程部分只是用来处理网络数据的读写和协议解析，执行命令仍然是单线程</strong></p>
<p>之所以这么设计是不想因为多线程而变得复杂，需要去控制 key、lua、事务，LPUSH&#x2F;LPOP 等等的并发问题</p>
<p>整体的设计大体如下</p>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201710017.png" alt="image-20221013201710017"></p>
<p>另外，多线程 IO 默认也是不开启的，需要再配置文件中配置</p>
<p><code>io-threads-do-reads yes</code></p>
<p><code>io-threads 4</code></p>
</blockquote>
</li>
</ul>
</li>
<li><p>工具支持 Cluster</p>
<blockquote>
<p>之前老版 Redis 想要搭集群需要单独安装 ruby 环境，Redis5 将 <code>redis-trib.rb</code> 的功能集成到 redis-cli </p>
<p>另外官方 redis-benchmark 工具开始支持 cluster 模式了，通过多线程的方式对多个分片进行压测</p>
</blockquote>
<p><img src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/image-20221013201810195.png" alt="image-20221013201810195"></p>
</li>
<li><p>其他新功能</p>
<ul>
<li><p>RESP3 新的 Redis 通信协议</p>
<blockquote>
<p>优化服务端与客户端之间通信</p>
</blockquote>
</li>
<li><p>Client side caching 客户端缓存</p>
<blockquote>
<p>基于 RESP3 协议实现的客户端缓存功能</p>
<p>为了进一步提升缓存的性能，将客户端经常访问的数据 cache 到客户端</p>
<p>减少TCP网络交互</p>
</blockquote>
</li>
<li><p>Proxy 集群代理模式</p>
<blockquote>
<p>Proxy 功能，让 Cluster 拥有像单实例一样的接入方式，降低大家使用 cluster 的门槛</p>
<p>不过需要注意的是代理不改变 Cluster 的功能限制，不支持的命令还是不会支持，比如跨 slot 的多 Key 操作</p>
</blockquote>
</li>
<li><p>Modules API</p>
<blockquote>
<p>Redis6 中模块 API 开发进展非常大，因为 Redis Labs 为了开发复杂的功能，从一开始就用上 Redis 模块</p>
<p>Redis 可以变成一个框架，利用 Modules 来构建不同系统，而不需要从头开始写然后还要BSD 许可</p>
<p>Redis 一开始就是一个向编写各种系统开放的平台</p>
</blockquote>
</li>
</ul>
</li>
</ol>
</article><div class="post-copyright"><div class="post-copyright__author"><span class="post-copyright-meta">文章作者: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm">zcm</a></span></div><div class="post-copyright__type"><span class="post-copyright-meta">文章链接: </span><span class="post-copyright-info"><a href="https://gitee.com/zcmmmm/zcmmmm/2022/10/12/Redis6%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/">https://gitee.com/zcmmmm/zcmmmm/2022/10/12/Redis6%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86/</a></span></div><div class="post-copyright__notice"><span class="post-copyright-meta">版权声明: </span><span class="post-copyright-info">本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank">CC BY-NC-SA 4.0</a> 许可协议。转载请注明来自 <a href="https://gitee.com/zcmmmm/zcmmmm" target="_blank">无人深空</a>！</span></div></div><div class="tag_share"><div class="post-meta__tag-list"><a class="post-meta__tags" href="/tags/Redis/">Redis</a><a class="post-meta__tags" href="/tags/DataBase/">DataBase</a></div><div class="post_share"><div class="social-share" data-image="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/Redis.png" data-sites="facebook,twitter,wechat,weibo,qq"></div><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/css/share.min.css" media="print" onload="this.media='all'"><script src="https://cdn.jsdelivr.net/npm/butterfly-extsrc/sharejs/dist/js/social-share.min.js" defer></script></div></div><nav class="pagination-post" id="pagination"><div class="prev-post pull-left"><a href="/2022/10/14/Redis%E9%9D%A2%E7%BB%8F/"><img class="prev-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/Redis.png" onerror="onerror=null;src='/img/404.jpg'" alt="cover of previous post"><div class="pagination-info"><div class="label">上一篇</div><div class="prev_info">Redis面经</div></div></a></div><div class="next-post pull-right"><a href="/2022/10/11/JVM%20%E4%B8%8B%E7%AF%87-%E6%80%A7%E8%83%BD%E7%9B%91%E6%8E%A7%E4%B8%8E%E8%B0%83%E4%BC%98%E7%AF%87/"><img class="next-cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/JVM/JVM.jpg" onerror="onerror=null;src='/img/404.jpg'" alt="cover of next post"><div class="pagination-info"><div class="label">下一篇</div><div class="next_info">JVM 下篇-性能监控与调优篇</div></div></a></div></nav><div class="relatedPosts"><div class="headline"><i class="fas fa-thumbs-up fa-fw"></i><span>相关推荐</span></div><div class="relatedPosts-list"><div><a href="/2022/10/14/Redis%E9%9D%A2%E7%BB%8F/" title="Redis面经"><img class="cover" src="https://study-record-images.oss-cn-beijing.aliyuncs.com/Java/Redis/Redis.png" alt="cover"><div class="content is-center"><div class="date"><i class="far fa-calendar-alt fa-fw"></i> 2022-10-14</div><div class="title">Redis面经</div></div></a></div></div></div></div><div class="aside-content" id="aside-content"><div class="sticky_layout"><div class="card-widget" id="card-toc"><div class="item-headline"><i class="fas fa-stream"></i><span>目录</span><span class="toc-percentage"></span></div><div class="toc-content is-expand"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#Redis6-%E5%85%A5%E9%97%A8%E5%88%B0%E7%B2%BE%E9%80%9A"><span class="toc-text">Redis6 入门到精通</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#1-NoSQL%E6%95%B0%E6%8D%AE%E5%BA%93%E7%AE%80%E4%BB%8B"><span class="toc-text">1.NoSQL数据库简介</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2-Redis6-%E6%A6%82%E8%BF%B0%E5%92%8C%E5%AE%89%E8%A3%85"><span class="toc-text">2.Redis6 概述和安装</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3-%E5%B8%B8%E7%94%A8%E4%BA%94%E5%A4%A7%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="toc-text">3.常用五大数据类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#4-Redis6-%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%AF%A6%E8%A7%A3"><span class="toc-text">4.Redis6 配置文件详解</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#5-Redis6-%E7%9A%84%E5%8F%91%E5%B8%83%E5%92%8C%E8%AE%A2%E9%98%85"><span class="toc-text">5.Redis6 的发布和订阅</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#6-Redis6-%E6%96%B0%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B"><span class="toc-text">6.Redis6 新数据类型</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#7-Jedis-%E6%93%8D%E4%BD%9C-Redis6"><span class="toc-text">7.Jedis 操作 Redis6</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#8-Redis6-%E4%B8%8E-Spring-Boot-%E6%95%B4%E5%90%88"><span class="toc-text">8.Redis6 与 Spring Boot 整合</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#9-Redis6-%E7%9A%84%E4%BA%8B%E5%8A%A1%E6%93%8D%E4%BD%9C"><span class="toc-text">9.Redis6 的事务操作</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E7%A7%92%E6%9D%80%E6%A1%88%E4%BE%8B"><span class="toc-text">秒杀案例</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#10-Redis6-%E6%8C%81%E4%B9%85%E5%8C%96%E4%B9%8B-RDB%EF%BC%88Redis-Database-Backup-file%EF%BC%89"><span class="toc-text">10.Redis6 持久化之 RDB（Redis Database Backup file）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#11-Redis6-%E6%8C%81%E4%B9%85%E5%8C%96%E4%B9%8B-AOF%EF%BC%88Append-Only-File%EF%BC%89"><span class="toc-text">11.Redis6 持久化之 AOF（Append Only File）</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#12-Redis6-%E7%9A%84%E4%B8%BB%E4%BB%8E%E5%A4%8D%E5%88%B6"><span class="toc-text">12.Redis6 的主从复制</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#13-Redis6-%E9%9B%86%E7%BE%A4"><span class="toc-text">13.Redis6 集群</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#14-Redis6-%E5%BA%94%E7%94%A8%E9%97%AE%E9%A2%98%E8%A7%A3%E5%86%B3"><span class="toc-text">14.Redis6 应用问题解决</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#15-Reids6-%E6%96%B0%E5%8A%9F%E8%83%BD"><span class="toc-text">15.Reids6 新功能</span></a></li></ol></li></ol></div></div></div></div></main><footer id="footer"><div id="footer-wrap"><div class="copyright">&copy;2022 - 2023 By zcm</div><div class="framework-info"><span>框架 </span><a target="_blank" rel="noopener" href="https://hexo.io">Hexo</a><span class="footer-separator">|</span><span>主题 </span><a target="_blank" rel="noopener" href="https://github.com/jerryc127/hexo-theme-butterfly">Butterfly</a></div></div></footer></div><div id="rightside"><div id="rightside-config-hide"><button id="readmode" type="button" title="阅读模式"><i class="fas fa-book-open"></i></button><button id="darkmode" type="button" title="浅色和深色模式转换"><i class="fas fa-adjust"></i></button><button id="hide-aside-btn" type="button" title="单栏和双栏切换"><i class="fas fa-arrows-alt-h"></i></button></div><div id="rightside-config-show"><button id="rightside_config" type="button" title="设置"><i class="fas fa-cog fa-spin"></i></button><button class="close" id="mobile-toc-button" type="button" title="目录"><i class="fas fa-list-ul"></i></button><button id="go-up" type="button" title="回到顶部"><i class="fas fa-arrow-up"></i></button></div></div><div id="local-search"><div class="search-dialog"><nav class="search-nav"><span class="search-dialog-title">搜索</span><span id="loading-status"></span><button class="search-close-button"><i class="fas fa-times"></i></button></nav><div class="is-center" id="loading-database"><i class="fas fa-spinner fa-pulse"></i><span>  数据库加载中</span></div><div class="search-wrap"><div id="local-search-input"><div class="local-search-box"><input class="local-search-box--input" placeholder="搜索文章" type="text"/></div></div><hr/><div id="local-search-results"></div></div></div><div id="search-mask"></div></div><div><script src="/js/utils.js"></script><script src="/js/main.js"></script><script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui/dist/fancybox.umd.min.js"></script><script src="https://cdn.jsdelivr.net/npm/node-snackbar/dist/snackbar.min.js"></script><script src="/js/search/local-search.js"></script><div class="js-pjax"></div><script src="/js/categories.js?v1"></script><script src="/js/navigation.js?v1"></script><script async data-pjax src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script></div></body></html>